From 1228c8ab5d245061f3a28caa2123108f1196d782 Mon Sep 17 00:00:00 2001 From: Younes Date: Tue, 26 Nov 2024 14:35:37 +0100 Subject: [PATCH] feat: better-auth migration --- apps/frontend/package.json | 7 +- apps/frontend/src/App.tsx | 56 +- apps/frontend/src/components/AppBarMenu.tsx | 29 +- apps/frontend/src/components/SharedLayout.tsx | 2 +- apps/frontend/src/components/SigninMenu.tsx | 16 +- .../src/components/auth/SignupDialog.tsx | 32 +- apps/frontend/src/lib/auth-client.ts | 15 + apps/frontend/src/pages/home.tsx | 6 +- apps/frontend/src/server/main.ts | 70 +- apps/frontend/src/state/recoil/project.ts | 4 +- apps/frontend/tsconfig.json | 3 + package.json | 1 + packages/{passport => auth}/package.json | 34 +- packages/auth/prisma/schema.prisma | 74 ++ packages/auth/src/auth.ts | 61 ++ packages/auth/src/env.ts | 10 + packages/auth/src/express.ts | 8 + packages/auth/src/index.ts | 3 + packages/auth/tsconfig.json | 14 + packages/{passport => auth}/tsup.config.ts | 6 +- packages/passport/src/errors.ts | 33 - packages/passport/src/index.ts | 6 - packages/passport/src/passport.ts | 72 -- packages/passport/src/session.ts | 33 - packages/passport/tsconfig.json | 18 - .../20241125105741_migrate_auth/migration.sql | 143 ++++ packages/prisma/package.json | 2 +- packages/prisma/schema.prisma | 63 +- packages/trpc/package.json | 5 +- packages/trpc/src/routers/annotation.ts | 24 +- packages/trpc/src/routers/chapter.ts | 15 +- packages/trpc/src/routers/playlist.ts | 3 +- packages/trpc/src/routers/project.ts | 16 +- packages/trpc/src/routers/user.ts | 128 ++- packages/trpc/src/trpc.ts | 51 +- .../utils/tsup.config.bundled_r6ojex3yf2.mjs | 0 pnpm-lock.yaml | 766 ++++++++++++++---- 37 files changed, 1224 insertions(+), 605 deletions(-) create mode 100644 apps/frontend/src/lib/auth-client.ts rename packages/{passport => auth}/package.json (58%) create mode 100644 packages/auth/prisma/schema.prisma create mode 100644 packages/auth/src/auth.ts create mode 100644 packages/auth/src/env.ts create mode 100644 packages/auth/src/express.ts create mode 100644 packages/auth/src/index.ts create mode 100644 packages/auth/tsconfig.json rename packages/{passport => auth}/tsup.config.ts (77%) delete mode 100644 packages/passport/src/errors.ts delete mode 100644 packages/passport/src/index.ts delete mode 100644 packages/passport/src/passport.ts delete mode 100644 packages/passport/src/session.ts delete mode 100644 packages/passport/tsconfig.json create mode 100644 packages/prisma/migrations/20241125105741_migrate_auth/migration.sql create mode 100644 packages/utils/tsup.config.bundled_r6ojex3yf2.mjs diff --git a/apps/frontend/package.json b/apps/frontend/package.json index d515b296..793897e6 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -24,8 +24,8 @@ "@adminjs/express": "^6.1.0", "@adminjs/prisma": "^5.0.3", "@adminjs/themes": "^1.0.1", + "@celluloid/auth": "workspace:*", "@celluloid/prisma": "workspace:*", - "@celluloid/passport": "workspace:*", "@celluloid/queue": "workspace:*", "@celluloid/react-player": "2.14.0", "@celluloid/trpc": "workspace:*", @@ -48,6 +48,7 @@ "@types/linkify-urls": "^3.1.1", "@uidotdev/usehooks": "^2.4.1", "adminjs": "^7.8.13", + "better-auth": "^1.0.3", "change-case": "^4.1.2", "cookie-parser": "^1.4.7", "copy-to-clipboard": "^3.3.3", @@ -56,7 +57,6 @@ "enzyme": "^3.3.0", "express": "^4.21.1", "express-formidable": "^1.2.0", - "express-session": "^1.18.1", "file-saver": "^2.0.5", "formik": "^2.2.9", "get-urls": "^11.0.0", @@ -69,8 +69,6 @@ "moment-duration-format": "^2.2.2", "mui-image": "^1.0.7", "notistack": "^3.0.1", - "passport": "^0.6.0", - "passport-local": "^1.0.0", "query-string": "^6.1.0", "ramda": "^0.28.0", "randomcolor": "^0.5.3", @@ -116,7 +114,6 @@ "@types/cors": "^2.8.13", "@types/express": "^4", "@types/express-formidable": "^1", - "@types/express-session": "^1", "@types/file-saver": "^2.0.5", "@types/get-urls": "^9.1.3", "@types/i18next": "^13.0.0", diff --git a/apps/frontend/src/App.tsx b/apps/frontend/src/App.tsx index 83954fb1..a047bdc9 100644 --- a/apps/frontend/src/App.tsx +++ b/apps/frontend/src/App.tsx @@ -1,6 +1,6 @@ import "dayjs/locale/fr"; // import locale -import { CssBaseline, Dialog, ThemeProvider } from "@mui/material"; +import { CssBaseline, ThemeProvider } from "@mui/material"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { createWSClient, httpBatchLink, splitLink, wsLink } from "@trpc/client"; @@ -16,7 +16,6 @@ import React, { Suspense, useCallback, useState } from "react"; import { initReactI18next } from "react-i18next"; import { BrowserRouter, - createBrowserRouter, Navigate, Route, Routes, @@ -55,10 +54,6 @@ import { createTheme } from "./theme"; const API_URL = "/api/trpc"; -const WS_URL = `${location.protocol === "https:" ? "wss" : "ws"}://${ - location.host -}/trpc`; - dayjs.extend(relativeTime); dayjs.extend(isLeapYear); // use plugin dayjs.extend(duration); @@ -166,28 +161,37 @@ const App = () => { const [trpcClient] = useState(() => trpc.createClient({ links: [ - splitLink({ - condition(op) { - // check for operation type - return op.type === "subscription"; + httpBatchLink({ + url: "/api/trpc", + fetch(url, options) { + return fetch(url, { + ...options, + credentials: "include", + }); }, - // when condition is true, use normal request - true: wsLink({ - client: createWSClient({ - url: WS_URL, - }), - }), - // when condition is false, use batching - false: httpBatchLink({ - url: API_URL, - fetch(url, options) { - return fetch(url, { - ...options, - credentials: "include", - }); - }, - }), }), + // splitLink({ + // condition(op) { + // // check for operation type + // return op.type === "subscription"; + // }, + // // when condition is true, use normal request + // true: wsLink({ + // client: createWSClient({ + // url: WS_URL, + // }), + // }), + // // when condition is false, use batching + // false: httpBatchLink({ + // url: API_URL, + // fetch(url, options) { + // return fetch(url, { + // ...options, + // credentials: "include", + // }); + // }, + // }), + // }), ], }) ); diff --git a/apps/frontend/src/components/AppBarMenu.tsx b/apps/frontend/src/components/AppBarMenu.tsx index cf752cc3..64f4cc71 100644 --- a/apps/frontend/src/components/AppBarMenu.tsx +++ b/apps/frontend/src/components/AppBarMenu.tsx @@ -1,38 +1,41 @@ -import { AppBar, Box, BoxProps, Button, styled, Toolbar } from "@mui/material"; -import * as React from "react"; +import { + AppBar, + Box, + type BoxProps, + Button, + styled, + Toolbar, +} from "@mui/material"; +import type * as React from "react"; import { useTranslation } from "react-i18next"; -import { useLocation, useNavigate } from "react-router"; +import { useNavigate } from "react-router"; import { getButtonLink } from "~components/ButtonLink"; import { Footer } from "~components/Footer"; import { LogoWithLabel } from "~components/LogoWithLabel"; import { SigninMenu } from "~components/SigninMenu"; -import { trpc } from "~utils/trpc"; import { LanguageMenu } from "./LanguageMenu"; +import { useSession } from "~/lib/auth-client"; const Offset = styled("div")(({ theme }) => theme.mixins.toolbar); export const AppBarMenu: React.FC = ({ children }) => { const { t } = useTranslation(); const navigate = useNavigate(); - const { data, isError } = trpc.user.me.useQuery( - {}, - { retry: false, keepPreviousData: false, cacheTime: 0 } - ); - const location = useLocation(); + const { data: session } = useSession(); const handleCreate = () => { - if (data) { - navigate(`/create`); + if (session) { + navigate("/create"); } else { navigate("/signup", { state: { backgroundLocation: "/" } }); } }; const handleJoin = () => { - if (!data) { + if (!session) { navigate("/signup-student", { state: { backgroundLocation: "/" } }); } else { navigate("/join", { state: { backgroundLocation: "/" } }); @@ -95,7 +98,7 @@ export const AppBarMenu: React.FC = ({ children }) => { {t("menu.about")} - + diff --git a/apps/frontend/src/components/SharedLayout.tsx b/apps/frontend/src/components/SharedLayout.tsx index d44c776a..a7f2b744 100644 --- a/apps/frontend/src/components/SharedLayout.tsx +++ b/apps/frontend/src/components/SharedLayout.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import type * as React from "react"; import { Outlet } from "react-router-dom"; import { AppBarMenu } from "./AppBarMenu"; diff --git a/apps/frontend/src/components/SigninMenu.tsx b/apps/frontend/src/components/SigninMenu.tsx index 3438a92e..fa46ad3a 100644 --- a/apps/frontend/src/components/SigninMenu.tsx +++ b/apps/frontend/src/components/SigninMenu.tsx @@ -4,11 +4,10 @@ import * as React from "react"; import { useTranslation } from "react-i18next"; import { useLocation, useNavigate } from "react-router"; -import { trpc, UserMe } from "~utils/trpc"; - import { Avatar } from "./Avatar"; +import { signOut, type User } from "~/lib/auth-client"; -export const SigninMenu = ({ user }: { user: UserMe }) => { +export const SigninMenu = ({ user }: { user: User }) => { const navigate = useNavigate(); const location = useLocation(); @@ -17,9 +16,6 @@ export const SigninMenu = ({ user }: { user: UserMe }) => { const [anchorEl, setAnchorEl] = React.useState(null); const open = Boolean(anchorEl); - const utils = trpc.useContext(); - const mutation = trpc.user.logout.useMutation(); - const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; @@ -28,9 +24,7 @@ export const SigninMenu = ({ user }: { user: UserMe }) => { }; const handleLogout = async () => { - await mutation.mutateAsync(); - utils.user.me.invalidate(); - utils.project.list.invalidate(); + await signOut(); handleClose(); navigate("/", { replace: true }); }; @@ -69,7 +63,7 @@ export const SigninMenu = ({ user }: { user: UserMe }) => { borderColor: user.color, borderStyle: "solid", }} - src={user.avatar?.publicUrl} + src={user.image ?? undefined} > {user.initial} @@ -136,7 +130,7 @@ export const SigninMenu = ({ user }: { user: UserMe }) => { }, }} > - {user && user.role == "Admin" ? ( + {user && user.role === "admin" ? ( {t("menu.admin")} diff --git a/apps/frontend/src/components/auth/SignupDialog.tsx b/apps/frontend/src/components/auth/SignupDialog.tsx index 231749e7..3c556279 100644 --- a/apps/frontend/src/components/auth/SignupDialog.tsx +++ b/apps/frontend/src/components/auth/SignupDialog.tsx @@ -1,17 +1,11 @@ import { LoadingButton } from "@mui/lab"; -import { - Alert, - Box, - Button, - DialogActions, - DialogContent, - Stack, -} from "@mui/material"; +import { DialogActions, DialogContent } from "@mui/material"; import TextField from "@mui/material/TextField"; import { useFormik } from "formik"; import { Trans, useTranslation } from "react-i18next"; -import { useLocation, useNavigate } from "react-router"; +import { useNavigate } from "react-router"; import * as Yup from "yup"; +import { signUp } from "~/lib/auth-client"; import { StyledDialog } from "~components/Dialog"; import { isTRPCClientError, trpc } from "~utils/trpc"; @@ -19,7 +13,6 @@ import { isTRPCClientError, trpc } from "~utils/trpc"; export const SignupDialog: React.FC = () => { const { t } = useTranslation(); const navigate = useNavigate(); - const location = useLocation(); const utils = trpc.useContext(); const mutation = trpc.user.register.useMutation(); @@ -48,16 +41,23 @@ export const SignupDialog: React.FC = () => { validateOnChange: true, onSubmit: async (values) => { try { - await mutation.mutateAsync({ - username: values.username, + // await mutation.mutateAsync({ + // username: values.username, + // email: values.email, + // password: values.password, + // }); + + const res = await signUp.email({ + name: values.username, email: values.email, password: values.password, }); + console.log(res); - utils.user.me.invalidate(); - navigate(`/confirm?email=${values.email}`, { - state: { backgroundLocation: "/" }, - }); + // utils.user.me.invalidate(); + // navigate(`/confirm?email=${values.email}`, { + // state: { backgroundLocation: "/" }, + // }); formik.setStatus("submited"); } catch (e) { if (isTRPCClientError(e)) { diff --git a/apps/frontend/src/lib/auth-client.ts b/apps/frontend/src/lib/auth-client.ts new file mode 100644 index 00000000..732d93c0 --- /dev/null +++ b/apps/frontend/src/lib/auth-client.ts @@ -0,0 +1,15 @@ +import { createAuthClient } from "better-auth/react"; +import { + emailOTPClient, + inferAdditionalFields, +} from "better-auth/client/plugins"; +import type { auth } from "@celluloid/auth"; + +export const authClient = createAuthClient({ + plugins: [emailOTPClient(), inferAdditionalFields()], +}); + +export type Session = typeof authClient.$Infer.Session; +export type User = typeof authClient.$Infer.Session.user; + +export const { signIn, signUp, signOut, useSession } = authClient; diff --git a/apps/frontend/src/pages/home.tsx b/apps/frontend/src/pages/home.tsx index f8a03c06..d5a1d61b 100644 --- a/apps/frontend/src/pages/home.tsx +++ b/apps/frontend/src/pages/home.tsx @@ -29,8 +29,6 @@ export const HomePage: React.FC = () => { { retry: false, keepPreviousData: false, cacheTime: 0 } ); - const location = useLocation(); - const { t } = useTranslation(); const navigate = useNavigate(); @@ -43,7 +41,7 @@ export const HomePage: React.FC = () => { }, [isError, navigate]); const handleCreate = () => { - navigate(`/create`); + navigate("/create"); }; return ( @@ -94,7 +92,7 @@ export const HomePage: React.FC = () => { gutterBottom={true} fontFamily={"abril_fatfaceregular"} > - + diff --git a/apps/frontend/src/server/main.ts b/apps/frontend/src/server/main.ts index ce020ec4..6bf9bde9 100644 --- a/apps/frontend/src/server/main.ts +++ b/apps/frontend/src/server/main.ts @@ -2,53 +2,34 @@ import express from "express"; import ViteExpress from "vite-express"; import * as trpcExpress from '@trpc/server/adapters/express'; import { appRouter, createContext } from '@celluloid/trpc'; -import type { Session } from 'express-session'; -import { createSession, passport } from '@celluloid/passport'; -import cookieParser from 'cookie-parser'; +import cookies from 'cookie-parser'; import cors from 'cors'; -const app = express(); + import { emailQueue, chaptersQueue } from "@celluloid/queue"; +import { auth } from "@celluloid/auth"; import getAdminRouter from "./admin"; -import { UserRole } from "@celluloid/prisma"; -const trpcApiEndpoint = '/api/trpc' +import { fromNodeHeaders, toNodeHandler } from "better-auth/node"; -declare module 'http' { - interface IncomingMessage { - session: Session & { - userId?: string - } - } -} - -const sessionParser = createSession(); +const app = express(); app.disable('x-powered-by'); app.use(express.json()); -app.use(express.urlencoded({ extended: true })); -app.use(sessionParser); +// app.use(express.urlencoded({ extended: true })); app.enable('trust proxy'); // parse cookies -app.use(cookieParser()); +app.use(cookies()); // Setup CORS -app.use(cors({ - origin: process.env.NODE_ENV !== "production" ? ['http://localhost:3000', 'http://localhost:4000'] : undefined, - credentials: true, -})); - -app.use((req, res, next) => { - //@ts-expect-error dynamic - passport.authenticate('session', (err) => { - if (err && err.name === "DeserializeUserError") { - req.session.destroy(() => - next()) - } - })(req, res, next); -}); +// app.use(cors({ +// origin: process.env.NODE_ENV !== "production" ? ['http://localhost:3000', 'http://localhost:4000'] : undefined, +// credentials: true, +// })); + +app.all("/api/auth/*", toNodeHandler(auth)); // app.use((req, _res, next) => { // // request logger @@ -56,20 +37,31 @@ app.use((req, res, next) => { // next(); // }); +app.get("/api/me", async (req, res) => { + const session = await auth.api.getSession({ + headers: fromNodeHeaders(req.headers), + }); + return res.json(session); +}); + const adminRouter = await getAdminRouter(); -const isAuthenticated = (req: express.Request, res: express.Response, next: express.NextFunction): void => { - // biome-ignore lint/correctness/noVoidTypeReturn: - if (req.user && (req.user as { role?: UserRole }).role === UserRole.Admin) return next(); - res.redirect("/"); -}; +app.use("/admin", async (req, res, next) => { + const session = await auth.api.getSession({ + headers: fromNodeHeaders(req.headers), + }); + if (!session || session.user.role !== "admin") { + res.redirect("/"); + return; + } -app.use("/admin", isAuthenticated, adminRouter); + next(); +}, adminRouter); app.use( - trpcApiEndpoint, + "/api/trpc", trpcExpress.createExpressMiddleware({ router: appRouter, createContext, diff --git a/apps/frontend/src/state/recoil/project.ts b/apps/frontend/src/state/recoil/project.ts index 39c89e15..fadf45de 100644 --- a/apps/frontend/src/state/recoil/project.ts +++ b/apps/frontend/src/state/recoil/project.ts @@ -1,6 +1,6 @@ import { atom, useRecoilState, useRecoilValue, useResetRecoilState } from "recoil"; -import { PeerTubeVideoDataResult } from "~services/peertube"; +import type { PeerTubeVideoDataResult } from "~services/peertube"; export type ProjectFormInput = { title: string; @@ -12,7 +12,7 @@ export type ProjectFormInput = { }; export const projectInputInitialValueAtom = atom({ - key: `ProjectFormInput`, + key: "ProjectFormInput", default: { title: "", description: "", diff --git a/apps/frontend/tsconfig.json b/apps/frontend/tsconfig.json index decf7c86..1d0abe05 100644 --- a/apps/frontend/tsconfig.json +++ b/apps/frontend/tsconfig.json @@ -64,6 +64,9 @@ ], "~chapters/*": [ "src/components/chapters/*" + ], + "~lib/*": [ + "src/lib/*" ] } }, diff --git a/package.json b/package.json index b0c74854..15aeeeb6 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "--shortcuts to run commands in workspaces--": "", "frontend": "dotenv -- pnpm --filter frontend", "admin": "dotenv -- pnpm --filter admin", + "auth": "dotenv -- pnpm --filter auth", "backend": "dotenv -- pnpm --filter backend", "prisma": "dotenv -- pnpm --filter @celluloid/prisma", "docker:build": "docker build -t celluloid:latest ." diff --git a/packages/passport/package.json b/packages/auth/package.json similarity index 58% rename from packages/passport/package.json rename to packages/auth/package.json index 0c333529..db81fd7d 100644 --- a/packages/passport/package.json +++ b/packages/auth/package.json @@ -1,5 +1,5 @@ { - "name": "@celluloid/passport", + "name": "@celluloid/auth", "version": "3.0.0", "private": true, "license": "MIT", @@ -7,40 +7,38 @@ "repository": { "type": "git", "url": "https://github.com/celluloid-camp/celluloid.git", - "directory": "packages/passport" + "directory": "packages/auth" }, "bugs": { "url": "https://github.com/udecode/plate/celluloid/issues" }, - "exports": { - ".": { - "types": "./dist/src/index.d.ts", - "default": "./src/index.ts" - } - }, + "type": "module", + "main": "src/index.ts", + "module": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist/**/*" + ], "scripts": { - "build": "tsc", - "dev": "tsc" + "build": "tsup", + "dev": "tsup --watch", + "generate": "pnpx @better-auth/cli generate" }, "dependencies": { "@celluloid/prisma": "workspace:*", "@celluloid/utils": "workspace:*", + "@prisma/client": "^5.22.0", + "@t3-oss/env-core": "^0.11.1", "bcryptjs": "^2.4.3", - "connect-redis": "^7.1.0", + "better-auth": "^1.0.3", "express": "^4.19.2", - "express-session": "^1.17.3", - "passport": "^0.6.0", - "passport-local": "^1.0.0", - "redis": "^4.6.10" + "zod": "^3.23.8" }, "devDependencies": { "@celluloid/config": "workspace:*", "@types/bcryptjs": "^2.4.5", "@types/express": "^5.0.0", - "@types/express-session": "^1.17.8", "@types/node": "^18.14.2", - "@types/passport": "^1.0.14", - "@types/passport-local": "^1.0.37", "tsup": "^8.3.0", "typescript": "^5.6.2" } diff --git a/packages/auth/prisma/schema.prisma b/packages/auth/prisma/schema.prisma new file mode 100644 index 00000000..3c3cc709 --- /dev/null +++ b/packages/auth/prisma/schema.prisma @@ -0,0 +1,74 @@ + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model User { + id String @id + name String + email String + emailVerified Boolean + image String? + createdAt DateTime + updatedAt DateTime + + role String? + banned Boolean? + banReason String? + banExpires DateTime? + + @@unique([email]) + @@map("user") +} + +model Session { + id String @id + expiresAt DateTime + token String + createdAt DateTime + updatedAt DateTime + ipAddress String? + userAgent String? + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + impersonatedBy String? + + @@unique([token]) + @@map("session") +} + +model Account { + id String @id + accountId String + providerId String + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + accessToken String? + refreshToken String? + idToken String? + accessTokenExpiresAt DateTime? + refreshTokenExpiresAt DateTime? + scope String? + password String? + createdAt DateTime + updatedAt DateTime + + @@map("account") +} + +model Verification { + id String @id + identifier String + value String + expiresAt DateTime + createdAt DateTime? + updatedAt DateTime? + + @@map("verification") +} diff --git a/packages/auth/src/auth.ts b/packages/auth/src/auth.ts new file mode 100644 index 00000000..215f3b9d --- /dev/null +++ b/packages/auth/src/auth.ts @@ -0,0 +1,61 @@ +import { betterAuth } from "better-auth"; +import { prismaAdapter } from "better-auth/adapters/prisma"; +import { prisma } from "@celluloid/prisma"; +import { admin, emailOTP } from "better-auth/plugins"; + +export const auth = betterAuth({ + logger: { + level: "debug" + }, + database: prismaAdapter(prisma, { + provider: "postgresql", + }), + emailVerification: { + sendOnSignUp: true, + autoSignInAfterVerification: true + }, + emailAndPassword: { + enabled: true, + requireEmailVerification: true, + sendResetPassword: async ({ user, url, token }, request) => { + // await sendEmail({ + // to: user.email, + // subject: 'Reset your password', + // text: `Click the link to reset your password: ${url}` + // }) + console.log("sendResetPassword", user, url, token); + return + } + }, + user: { + additionalFields: { + role: { + type: "string", + required: false, + defaultValue: "user", + input: false // don't allow user to set role + }, + initial: { + type: "string", + required: false, + defaultValue: "CC", + input: false + }, + color: { + type: "string", + required: false, + defaultValue: "#000000", + input: false + }, + } + }, + plugins: [ + admin(), + emailOTP({ + async sendVerificationOTP({ email, otp, type }) { + // Implement the sendVerificationOTP method to send the OTP to the user's email address + console.log("sendVerificationOTP", email, otp, type); + }, + }) + ] +}); diff --git a/packages/auth/src/env.ts b/packages/auth/src/env.ts new file mode 100644 index 00000000..fb4711de --- /dev/null +++ b/packages/auth/src/env.ts @@ -0,0 +1,10 @@ +import { createEnv } from "@t3-oss/env-core"; +import { z } from "zod"; + +export const env = createEnv({ + server: { + BETTER_AUTH_SECRET: z.string(), + }, + runtimeEnv: process.env, + emptyStringAsUndefined: true, +}); diff --git a/packages/auth/src/express.ts b/packages/auth/src/express.ts new file mode 100644 index 00000000..e8ec0b1a --- /dev/null +++ b/packages/auth/src/express.ts @@ -0,0 +1,8 @@ + + +import { toNodeHandler } from "better-auth/node"; +import { auth } from "./auth"; + +export const expressAuthHandler = toNodeHandler(auth); + + diff --git a/packages/auth/src/index.ts b/packages/auth/src/index.ts new file mode 100644 index 00000000..50fa1495 --- /dev/null +++ b/packages/auth/src/index.ts @@ -0,0 +1,3 @@ +export * from "./auth"; +export * from "./env"; +export * from "./express"; diff --git a/packages/auth/tsconfig.json b/packages/auth/tsconfig.json new file mode 100644 index 00000000..73b617f7 --- /dev/null +++ b/packages/auth/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@celluloid/config/tsconfig/node16.json", + "compilerOptions": { + "resolveJsonModule": true + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "tsup.config.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/packages/passport/tsup.config.ts b/packages/auth/tsup.config.ts similarity index 77% rename from packages/passport/tsup.config.ts rename to packages/auth/tsup.config.ts index 5b98c812..fab89762 100644 --- a/packages/passport/tsup.config.ts +++ b/packages/auth/tsup.config.ts @@ -4,9 +4,9 @@ const isProduction = process.env.NODE_ENV === "production"; export default defineConfig({ clean: true, - dts: true, - entry: ["src/index.ts"], - format: ["esm"], + dts: false, + entry: ["src/index.ts",], + format: ['esm'], target: "esnext", minify: isProduction, sourcemap: true, diff --git a/packages/passport/src/errors.ts b/packages/passport/src/errors.ts deleted file mode 100644 index 992d9b59..00000000 --- a/packages/passport/src/errors.ts +++ /dev/null @@ -1,33 +0,0 @@ - - -export class InvalidUserError extends Error { - constructor(message = "Invalid User") { - super(message); - this.name = "InvalidUserError"; - - // This is necessary when extending native JavaScript classes like Error - Object.setPrototypeOf(this, InvalidUserError.prototype); - } -} - -export class DeserializeUserError extends Error { - constructor(message = "Invalid User") { - super(message); - this.name = "DeserializeUserError"; - - // This is necessary when extending native JavaScript classes like Error - Object.setPrototypeOf(this, InvalidUserError.prototype); - } -} - - - -export class UserNotConfirmed extends Error { - constructor(message = "User Not Confirmed") { - super(message); - this.name = "UserNotConfirmed"; - - // This is necessary when extending native JavaScript classes like Error - Object.setPrototypeOf(this, UserNotConfirmed.prototype); - } -} diff --git a/packages/passport/src/index.ts b/packages/passport/src/index.ts deleted file mode 100644 index 535d036f..00000000 --- a/packages/passport/src/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from "./errors"; -export { createSession } from "./session"; - -export { default as passport } from './passport'; -export * from "./passport"; - diff --git a/packages/passport/src/passport.ts b/packages/passport/src/passport.ts deleted file mode 100644 index 61834809..00000000 --- a/packages/passport/src/passport.ts +++ /dev/null @@ -1,72 +0,0 @@ - -import { prisma, type User, UserRole } from "@celluloid/prisma" -import bcrypt from 'bcryptjs'; -import passport from 'passport'; -import { - Strategy as LocalStrategy, -} from "passport-local"; - -import { DeserializeUserError, InvalidUserError, UserNotConfirmed } from "./errors"; - -// export enum SigninStrategy { -// LOGIN = "login", -// TEACHER_SIGNUP = "teacher-signup", -// STUDENT_SIGNUP = "student-signup", -// } - - -passport.serializeUser((user, done) => { - done(null, (user as User).id) -}); - -passport.deserializeUser(async (id: string, done) => { - const user = await prisma.user.findUnique({ where: { id } }) - if (user) { - return done(null, user); - } - return done(new DeserializeUserError("Deserialize user failed: user does not exist")); - -}); - -passport.use( - new LocalStrategy(async (username: string, password: string, done) => { - const user = await prisma.user.findUnique({ where: { username: username } }) - if (!user) { - return done(new InvalidUserError("User not found")); - } - if (!bcrypt.compareSync(password, user.password)) { - return Promise.resolve(done(new InvalidUserError(`Login failed for user ${user.username}: incorrect password`))); - } - if (!user.confirmed && user.role !== UserRole.Student) { - return done(new UserNotConfirmed()); - } - return done(null, user); - - }), -); - - -const loginStrategy = new LocalStrategy(async (login, password, done) => { - - const user = await prisma.user.findFirst({ - where: { - OR: [{ email: login }, { username: login, }] - } - }); - - if (!user) { - return done(new InvalidUserError("User not found")); - } - if (!bcrypt.compareSync(password, user.password)) { - return done(new InvalidUserError(`Login failed for user ${user.username}: incorrect password`)); - } - if (!user.confirmed && user.role !== UserRole.Student) { - return done(new UserNotConfirmed(`Login failed: ${user.username} is not confirmed`)); - } - return done(null, user); -} -); - -passport.use("login", loginStrategy); - -export default passport; diff --git a/packages/passport/src/session.ts b/packages/passport/src/session.ts deleted file mode 100644 index 4efdc1a2..00000000 --- a/packages/passport/src/session.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { env } from "@celluloid/utils" -import RedisStore from "connect-redis" -import session from "express-session" -import { createClient } from "redis" - - -export function createSession(): ReturnType { - - // Initialize client. - const redisClient = createClient({ - url: env.REDIS_URL - }) - redisClient.connect().catch(console.error) - - // Initialize store. - const redisStore = new RedisStore({ - client: redisClient, - }) - - return session({ - store: redisStore, - name: env.COOKIE_NAME, - cookie: { - domain: env.COOKIE_DOMAIN, - secure: env.COOKIE_SECURE, - maxAge: 30 * 24 * 3600 * 1000, - httpOnly: true, - }, - secret: env.COOKIE_SECRET, - resave: false, - saveUninitialized: true, - }); -} diff --git a/packages/passport/tsconfig.json b/packages/passport/tsconfig.json deleted file mode 100644 index 490ddcef..00000000 --- a/packages/passport/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "@celluloid/config/tsconfig/internal-package.json", - "include": [ - "src" - ], - "exclude": [ - "node_modules" - ], - "compilerOptions": { - "noEmitOnError": false, - "skipLibCheck": true, - "skipDefaultLibCheck": true, - "noImplicitAny": false, - "strict": false, - "checkJs": false, - "allowJs": true - } -} diff --git a/packages/prisma/migrations/20241125105741_migrate_auth/migration.sql b/packages/prisma/migrations/20241125105741_migrate_auth/migration.sql new file mode 100644 index 00000000..65c7090e --- /dev/null +++ b/packages/prisma/migrations/20241125105741_migrate_auth/migration.sql @@ -0,0 +1,143 @@ +/* + Warnings: + + - You are about to drop the `User` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "Annotation" DROP CONSTRAINT "annotation_userid_foreign"; + +-- DropForeignKey +ALTER TABLE "Chapter" DROP CONSTRAINT "Chapter_lastEditedById_fkey"; + +-- DropForeignKey +ALTER TABLE "Comment" DROP CONSTRAINT "comment_userid_foreign"; + +-- DropForeignKey +ALTER TABLE "Playlist" DROP CONSTRAINT "Playlist_userId_fkey"; + +-- DropForeignKey +ALTER TABLE "Project" DROP CONSTRAINT "project_userid_foreign"; + +-- DropForeignKey +ALTER TABLE "User" DROP CONSTRAINT "User_avatarStorageId_fkey"; + +-- DropForeignKey +ALTER TABLE "UserToProject" DROP CONSTRAINT "usertoproject_userid_foreign"; + +-- DropTable +DROP TABLE "User"; + +-- CreateTable +CREATE TABLE "user" ( + "id" UUID NOT NULL DEFAULT gen_random_uuid(), + "email" VARCHAR(255), + "username" VARCHAR(255) NOT NULL, + "password" VARCHAR(255) NOT NULL, + "confirmed" BOOLEAN NOT NULL DEFAULT false, + "code" TEXT, + "codeGeneratedAt" TIMESTAMPTZ(6), + "extra" JSONB DEFAULT '{}', + "firstname" VARCHAR(255), + "lastname" VARCHAR(255), + "bio" TEXT, + "avatarStorageId" UUID, + "name" TEXT NOT NULL, + "emailVerified" BOOLEAN NOT NULL, + "image" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL, + "updatedAt" TIMESTAMP(3) NOT NULL, + "role" TEXT, + "banned" BOOLEAN, + "banReason" TEXT, + "banExpires" TIMESTAMP(3), + + CONSTRAINT "user_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "session" ( + "id" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + "token" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL, + "updatedAt" TIMESTAMP(3) NOT NULL, + "ipAddress" TEXT, + "userAgent" TEXT, + "userId" UUID NOT NULL, + + CONSTRAINT "session_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "account" ( + "id" TEXT NOT NULL, + "accountId" TEXT NOT NULL, + "providerId" TEXT NOT NULL, + "userId" UUID NOT NULL, + "accessToken" TEXT, + "refreshToken" TEXT, + "idToken" TEXT, + "accessTokenExpiresAt" TIMESTAMP(3), + "refreshTokenExpiresAt" TIMESTAMP(3), + "scope" TEXT, + "password" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "account_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "verification" ( + "id" TEXT NOT NULL, + "identifier" TEXT NOT NULL, + "value" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3), + "updatedAt" TIMESTAMP(3), + + CONSTRAINT "verification_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "user_id_unique" ON "user"("id"); + +-- CreateIndex +CREATE UNIQUE INDEX "user_email_unique" ON "user"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "user_username_unique" ON "user"("username"); + +-- CreateIndex +CREATE UNIQUE INDEX "user_avatarStorageId_key" ON "user"("avatarStorageId"); + +-- CreateIndex +CREATE UNIQUE INDEX "session_token_key" ON "session"("token"); + +-- AddForeignKey +ALTER TABLE "Annotation" ADD CONSTRAINT "annotation_userid_foreign" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "Comment" ADD CONSTRAINT "comment_userid_foreign" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "Project" ADD CONSTRAINT "project_userid_foreign" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "Chapter" ADD CONSTRAINT "Chapter_lastEditedById_fkey" FOREIGN KEY ("lastEditedById") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Playlist" ADD CONSTRAINT "Playlist_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "user" ADD CONSTRAINT "user_avatarStorageId_fkey" FOREIGN KEY ("avatarStorageId") REFERENCES "Storage"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "session" ADD CONSTRAINT "session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "account" ADD CONSTRAINT "account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UserToProject" ADD CONSTRAINT "usertoproject_userid_foreign" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION; diff --git a/packages/prisma/package.json b/packages/prisma/package.json index 1c9c9db8..259f119e 100644 --- a/packages/prisma/package.json +++ b/packages/prisma/package.json @@ -6,7 +6,7 @@ "repository": { "type": "git", "url": "https://github.com/celluloid-camp/celluloid.git", - "directory": "packages/passport" + "directory": "packages/prisma" }, "bugs": { "url": "https://github.com/udecode/plate/celluloid/issues" diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 6203f6d1..1917207d 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -128,7 +128,6 @@ model User { confirmed Boolean @default(false) code String? codeGeneratedAt DateTime? @db.Timestamptz(6) - role UserRole? extra Json? @default("{}") annotation Annotation[] comment Comment[] @@ -145,6 +144,68 @@ model User { avatarStorageId String? @unique @db.Uuid chapters Chapter[] + + name String + // email String + emailVerified Boolean + image String? + createdAt DateTime + updatedAt DateTime + + sessions Session[] + accounts Account[] + + role String? + banned Boolean? + banReason String? + banExpires DateTime? + + @@map("user") +} + +model Session { + id String @id + expiresAt DateTime + token String + createdAt DateTime + updatedAt DateTime + ipAddress String? + userAgent String? + userId String @db.Uuid + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([token]) + @@map("session") +} + +model Account { + id String @id + accountId String + providerId String + userId String @db.Uuid + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + accessToken String? + refreshToken String? + idToken String? + accessTokenExpiresAt DateTime? + refreshTokenExpiresAt DateTime? + scope String? + password String? + createdAt DateTime + updatedAt DateTime + + @@map("account") +} + +model Verification { + id String @id + identifier String + value String + expiresAt DateTime + createdAt DateTime? + updatedAt DateTime? + + @@map("verification") } /// The underlying table does not contain a valid unique identifier and can therefore currently not be handled by the Prisma Client. diff --git a/packages/trpc/package.json b/packages/trpc/package.json index f6e397f2..a28f6c86 100644 --- a/packages/trpc/package.json +++ b/packages/trpc/package.json @@ -24,17 +24,17 @@ "dev": "tsup --watch" }, "dependencies": { - "@celluloid/passport": "workspace:*", "@celluloid/prisma": "workspace:*", "@celluloid/queue": "workspace:*", "@celluloid/utils": "workspace:*", + "@celluloid/auth": "workspace:*", "@t3-oss/env-core": "^0.11.1", "@trpc/server": "^10.45.2", "bcryptjs": "^2.4.3", + "better-auth": "^1.0.3", "change-case": "^4.1.2", "dotenv": "^16.3.1", "express": "^4.19.2", - "express-session": "^1.17.3", "js2xmlparser": "^5.0.0", "lodash": "^4.17.21", "minio": "^7.1.3", @@ -50,7 +50,6 @@ "devDependencies": { "@celluloid/config": "workspace:*", "@types/express": "^5.0.0", - "@types/express-session": "^1.17.8", "@types/mjml": "^4.7.3", "@types/nodemailer": "^6.4.13", "@types/papaparse": "^5.3.10", diff --git a/packages/trpc/src/routers/annotation.ts b/packages/trpc/src/routers/annotation.ts index 039ac72c..ccafab37 100644 --- a/packages/trpc/src/routers/annotation.ts +++ b/packages/trpc/src/routers/annotation.ts @@ -1,4 +1,4 @@ -import { prisma, UserRole } from '@celluloid/prisma'; +import { prisma } from '@celluloid/prisma'; import type { Annotation } from '@celluloid/prisma'; import { Prisma } from '@celluloid/prisma'; import { toSrt } from '@celluloid/utils'; @@ -97,7 +97,7 @@ export const annotationRouter = router({ }), ) .mutation(async ({ input, ctx }) => { - if (ctx.user && ctx.user.id) { + if (ctx.user?.id) { const annotation = await prisma.annotation.create({ data: { userId: ctx.user?.id, @@ -146,7 +146,7 @@ export const annotationRouter = router({ ); } - if (annotation.userId == ctx.user?.id || ctx.user.role == UserRole.Admin || annotation.project.userId == ctx.user?.id) { + if (annotation.userId === ctx.user?.id || ctx.user?.role === "admin" || annotation.project.userId === ctx.user?.id) { // Perform the update const updatedAnnotation = await prisma.annotation.update({ where: { id: input.annotationId }, @@ -195,20 +195,18 @@ export const annotationRouter = router({ ); } - if (annotation.userId == ctx.user?.id || ctx.user.role == UserRole.Admin || annotation.project.userId == ctx.user?.id) { + if (annotation.userId === ctx.user?.id || ctx.user?.role === "admin" || annotation.project.userId === ctx.user?.id) { const annotation = await prisma.annotation.delete({ where: { id: input.annotationId }, }); ee.emit('change', annotation); return annotation; - } else { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "Can't edit this annotation" - } - ); - } + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Can't edit this annotation" + } + ); }), export: protectedProcedure .input( @@ -241,10 +239,10 @@ export const annotationRouter = router({ let content = ""; if (format === 'xml') { content = toXML("annotations", formated, { cdataKeys: ['comments', 'text'] }); - } else if (format == "csv") { + } else if (format === "csv") { const sorted = formated.sort((a, b) => a.startTime - b.startTime) content = Papa.unparse(sorted); - } else if (format == "srt") { + } else if (format === "srt") { content = toSrt(formated); } return content diff --git a/packages/trpc/src/routers/chapter.ts b/packages/trpc/src/routers/chapter.ts index 5bbc9743..a6f3893b 100644 --- a/packages/trpc/src/routers/chapter.ts +++ b/packages/trpc/src/routers/chapter.ts @@ -1,12 +1,7 @@ -import { prisma, UserRole } from '@celluloid/prisma'; -import type { Annotation } from '@celluloid/prisma'; +import { prisma } from '@celluloid/prisma'; import { Prisma } from '@celluloid/prisma'; -import { toSrt } from '@celluloid/utils'; import { TRPCError } from '@trpc/server'; -import { observable } from '@trpc/server/observable'; import { EventEmitter } from 'node:events'; -import { parse as toXML } from 'js2xmlparser'; -import Papa from 'papaparse'; import { z } from 'zod'; import { protectedProcedure, publicProcedure, router } from '../trpc'; @@ -75,7 +70,7 @@ export const chapterRouter = router({ }), ) .mutation(async ({ input, ctx }) => { - if (ctx.user?.id && ctx.requirePermissions([UserRole.Teacher, UserRole.Admin])) { + if (ctx.user?.id && ctx.requireRoles(['teacher', 'admin'])) { // Find the project by its ID (you need to replace 'projectId' with the actual ID) const project = await prisma.project.findUnique({ @@ -132,7 +127,7 @@ export const chapterRouter = router({ ); } - if (ctx.user.role === UserRole.Admin || project.userId === ctx.user?.id) { + if (ctx.user?.role === "admin" || project.userId === ctx.user?.id) { const chapter = await prisma.chapter.create({ data: { projectId: input.projectId, @@ -190,7 +185,7 @@ export const chapterRouter = router({ ); } - if (ctx.user.role === UserRole.Admin || chapter.project.userId === ctx.user?.id) { + if (ctx.user?.role === "admin" || chapter.project.userId === ctx.user?.id) { // Perform the update const updatedChapter = await prisma.chapter.update({ where: { id: input.chapterId }, @@ -236,7 +231,7 @@ export const chapterRouter = router({ ); } - if (ctx.user.role === UserRole.Admin || chapter.project.userId === ctx.user?.id) { + if (ctx.user?.role === "admin" || chapter.project.userId === ctx.user?.id) { const chapter = await prisma.chapter.delete({ where: { id: input.chapterId }, }); diff --git a/packages/trpc/src/routers/playlist.ts b/packages/trpc/src/routers/playlist.ts index ab0e0af7..bd5c23b4 100644 --- a/packages/trpc/src/routers/playlist.ts +++ b/packages/trpc/src/routers/playlist.ts @@ -1,4 +1,3 @@ -import { UserRole } from '@celluloid/prisma'; import { prisma } from "@celluloid/prisma" import { generateUniqueShareName } from '@celluloid/utils'; import { TRPCError } from '@trpc/server'; @@ -143,7 +142,7 @@ export const playlistRouter = router({ }), ) .mutation(async ({ input, ctx }) => { - if (ctx.user && ctx.requirePermissions([UserRole.Teacher, UserRole.Admin])) { + if (ctx.user && ctx.requireRoles(['teacher', 'admin'])) { const userId = ctx.user.id; const project = await prisma.playlist.create({ diff --git a/packages/trpc/src/routers/project.ts b/packages/trpc/src/routers/project.ts index 82c23df5..6674b931 100644 --- a/packages/trpc/src/routers/project.ts +++ b/packages/trpc/src/routers/project.ts @@ -1,4 +1,4 @@ -import { Prisma, prisma, UserRole } from '@celluloid/prisma'; +import { Prisma, prisma } from '@celluloid/prisma'; import { generateUniqueShareName } from '@celluloid/utils'; import { TRPCError } from '@trpc/server'; import { z } from 'zod'; @@ -210,10 +210,10 @@ export const projectRouter = router({ return { ...project, - editable: ctx.user && (ctx.user.id === project.userId || ctx.user.role === UserRole.Admin), - deletable: ctx.user && (ctx.user.id === project.userId || ctx.user.role === UserRole.Admin), - annotable: ctx.user && (ctx.user.id === project.userId || ctx.user.role === UserRole.Admin || (project.members.some(m => ctx.user && m.userId === ctx.user.id) && project.collaborative)), - commentable: ctx.user && (ctx.user.id === project.userId || ctx.user.role === UserRole.Admin || (project.members.some(m => ctx.user && m.userId === ctx.user.id) && project.collaborative)), + editable: ctx.user && (ctx.user.id === project.userId || ctx.user.role === "admin"), + deletable: ctx.user && (ctx.user.id === project.userId || ctx.user.role === "admin"), + annotable: ctx.user && (ctx.user.id === project.userId || ctx.user.role === "admin" || (project.members.some(m => ctx.user && m.userId === ctx.user.id) && project.collaborative)), + commentable: ctx.user && (ctx.user.id === project.userId || ctx.user.role === "admin" || (project.members.some(m => ctx.user && m.userId === ctx.user.id) && project.collaborative)), }; }), add: protectedProcedure @@ -236,7 +236,7 @@ export const projectRouter = router({ }), ) .mutation(async ({ input, ctx }) => { - if (ctx.user?.id && ctx.requirePermissions([UserRole.Teacher, UserRole.Admin])) { + if (ctx.user?.id && ctx.requireRoles(['teacher', 'admin'])) { const project = await prisma.project.create({ data: { @@ -287,7 +287,7 @@ export const projectRouter = router({ }), ) .mutation(async ({ input, ctx }) => { - if (ctx.user?.id && ctx.requirePermissions([UserRole.Teacher, UserRole.Admin])) { + if (ctx.user?.id && ctx.requireRoles(['teacher', 'admin'])) { // Find the project by its ID (you need to replace 'projectId' with the actual ID) const project = await prisma.project.findUnique({ @@ -340,7 +340,7 @@ export const projectRouter = router({ }), ) .mutation(async ({ input, ctx }) => { - if (ctx.user?.id && ctx.requirePermissions([UserRole.Teacher, UserRole.Admin])) { + if (ctx.user?.id && ctx.requireRoles(['teacher', 'admin'])) { // Find the project by its ID (you need to replace 'projectId' with the actual ID) const project = await prisma.project.findUnique({ diff --git a/packages/trpc/src/routers/user.ts b/packages/trpc/src/routers/user.ts index c8600559..1e367b6d 100644 --- a/packages/trpc/src/routers/user.ts +++ b/packages/trpc/src/routers/user.ts @@ -1,5 +1,4 @@ -import { passport } from "@celluloid/passport"; -import { Prisma, prisma, UserRole } from "@celluloid/prisma" +import { Prisma, prisma } from "@celluloid/prisma" import { compareCodes, generateOtp, hashPassword } from "@celluloid/utils"; import { TRPCError } from "@trpc/server"; import bcrypt from 'bcryptjs'; @@ -32,71 +31,71 @@ export const defaultUserSelect = Prisma.validator()({ export const UserSchema = z.object({ id: z.string({ description: 'The unique identifier for the user' }), username: z.string({ description: 'The username for the user' }), - role: z.nativeEnum(UserRole, { description: 'The role assigned to the user, either Admin or User' }).nullable(), + role: z.string({ description: 'The role assigned to the user, either Admin or User' }).nullable(), initial: z.string({ description: 'The initial letter or string for user representation' }), color: z.string({ description: 'The color code associated with the user' }) }); export const userRouter = router({ - login: publicProcedure - .meta({ - openapi: { - method: 'POST', - path: '/login', - description: 'This endpoint allows a user to login.' - } - }) - .input( - z.object({ - username: z.string({ description: 'The username of the user' }), - password: z.string({ description: 'The password for the user' }) - }), - ).output(UserSchema.nullable()) - .mutation(async ({ ctx, input }) => { - - //@ts-expect-error dynamic - ctx.req.body = input; - - emailQueue.add({ email: input.username }); - await new Promise((resolve, reject) => { - passport.authenticate("login", { - failWithError: true - })(ctx.req, ctx.res, (err: Error, user: Express.User) => { - if (err) return reject(err); - resolve(user); - }) - }).catch(err => { - console.log(err.name); - - if (err?.name === 'InvalidUserError') { - throw new TRPCError({ - code: 'UNAUTHORIZED', - message: 'USER_NOT_FOUND' - }) - } - if (err?.name === "UserNotConfirmed") { - throw new TRPCError({ - code: 'UNAUTHORIZED', - message: 'USER_NOT_CONFIRMED' - }) - } - - throw new TRPCError({ - code: 'INTERNAL_SERVER_ERROR', - message: err - }) - }) - - const user = await prisma.user.findFirst({ - select: defaultUserSelect, - where: { - OR: [{ email: input.username }, { username: input.username, }] - } - }); - - return user; - }), + // login: publicProcedure + // .meta({ + // openapi: { + // method: 'POST', + // path: '/login', + // description: 'This endpoint allows a user to login.' + // } + // }) + // .input( + // z.object({ + // username: z.string({ description: 'The username of the user' }), + // password: z.string({ description: 'The password for the user' }) + // }), + // ).output(UserSchema.nullable()) + // .mutation(async ({ ctx, input }) => { + + // //@ts-expect-error dynamic + // ctx.req.body = input; + + // emailQueue.add({ email: input.username }); + // await new Promise((resolve, reject) => { + // passport.authenticate("login", { + // failWithError: true + // })(ctx.req, ctx.res, (err: Error, user: Express.User) => { + // if (err) return reject(err); + // resolve(user); + // }) + // }).catch(err => { + // console.log(err.name); + + // if (err?.name === 'InvalidUserError') { + // throw new TRPCError({ + // code: 'UNAUTHORIZED', + // message: 'USER_NOT_FOUND' + // }) + // } + // if (err?.name === "UserNotConfirmed") { + // throw new TRPCError({ + // code: 'UNAUTHORIZED', + // message: 'USER_NOT_CONFIRMED' + // }) + // } + + // throw new TRPCError({ + // code: 'INTERNAL_SERVER_ERROR', + // message: err + // }) + // }) + + // const user = await prisma.user.findFirst({ + // select: defaultUserSelect, + // where: { + // OR: [{ email: input.username }, { username: input.username, }] + // } + // }); + + // return user; + // }), forgot: publicProcedure.input( z.object({ email: z.string() @@ -597,11 +596,6 @@ export const userRouter = router({ items: items.reverse(), nextCursor, }; - }), - logout: protectedProcedure - .mutation(async (opts) => { - const { ctx } = opts; - return ctx.logout(); - }), + }) }); diff --git a/packages/trpc/src/trpc.ts b/packages/trpc/src/trpc.ts index e8e96080..9ec999fb 100644 --- a/packages/trpc/src/trpc.ts +++ b/packages/trpc/src/trpc.ts @@ -1,40 +1,25 @@ -import "express-session" -import type { User, UserRole } from '@celluloid/prisma'; import { type inferAsyncReturnType, initTRPC, TRPCError } from '@trpc/server'; import type { CreateExpressContextOptions } from "@trpc/server/adapters/express"; import type { CreateWSSContextFnOptions, } from '@trpc/server/adapters/ws'; -import type { Request, Response } from 'express'; -import type { Session } from "express-session"; import type { OpenApiMeta } from 'trpc-openapi'; - -// export type Context = { -// user: User | null; - -// requirePermissions: (roles: UserRole[]) => boolean; -// logout: () => Promise; -// req: Request; -// res: Response; -// }; - -declare module 'http' { - interface IncomingMessage { - session: Session & { - userId?: string - } - } -} +import { auth } from "@celluloid/auth"; +import { fromNodeHeaders } from "better-auth/node"; export async function createContext(opts: CreateExpressContextOptions | CreateWSSContextFnOptions) { const { req, res } = opts; - const user: User | null = (req as Request).user as User; - const requirePermissions = (roles: UserRole[]) => { - if (!user?.role || !roles.includes(user?.role)) { + const session = await auth.api.getSession({ + headers: fromNodeHeaders(req.headers), + }); + + + const requireRoles = (roles: string[]) => { + if (!session?.user?.role || !roles.includes(session?.user?.role)) { throw new TRPCError({ code: 'UNAUTHORIZED', message: `Missing permission: '${roles.join(",")}'.` @@ -44,22 +29,10 @@ export async function createContext(opts: CreateExpressContextOptions | CreateWS return true; } - const logout = (): Promise => { - return new Promise((resolve) => { - req.session?.destroy((err: Error | null) => { - if (err) { - throw new TRPCError({ - code: 'BAD_REQUEST', - message: "Enable to log out" - }) - } - - resolve(true) - }); - }) - } + const user = session?.user; return { - user, requirePermissions, logout, req, + user, requireRoles, + req, res, }; }; diff --git a/packages/utils/tsup.config.bundled_r6ojex3yf2.mjs b/packages/utils/tsup.config.bundled_r6ojex3yf2.mjs new file mode 100644 index 00000000..e69de29b diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 75ac853c..1a2ca7bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -120,9 +120,9 @@ importers: '@adminjs/themes': specifier: ^1.0.1 version: 1.0.1(@types/babel__core@7.20.5)(@types/node@16.18.119)(adminjs@7.8.13(@types/babel__core@7.20.5)(@types/react-dom@18.3.1)(@types/react@18.3.12)(encoding@0.1.13))(tslib@2.8.1) - '@celluloid/passport': + '@celluloid/auth': specifier: workspace:* - version: link:../../packages/passport + version: link:../../packages/auth '@celluloid/prisma': specifier: workspace:* version: link:../../packages/prisma @@ -192,6 +192,9 @@ importers: adminjs: specifier: ^7.8.13 version: 7.8.13(@types/babel__core@7.20.5)(@types/react-dom@18.3.1)(@types/react@18.3.12)(encoding@0.1.13) + better-auth: + specifier: ^1.0.3 + version: 1.0.3(encoding@0.1.13) change-case: specifier: ^4.1.2 version: 4.1.2 @@ -216,9 +219,6 @@ importers: express-formidable: specifier: ^1.2.0 version: 1.2.0 - express-session: - specifier: ^1.18.1 - version: 1.18.1 file-saver: specifier: ^2.0.5 version: 2.0.5 @@ -255,12 +255,6 @@ importers: notistack: specifier: ^3.0.1 version: 3.0.1(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - passport: - specifier: ^0.6.0 - version: 0.6.0 - passport-local: - specifier: ^1.0.0 - version: 1.0.0 query-string: specifier: ^6.1.0 version: 6.14.1 @@ -355,9 +349,6 @@ importers: '@types/express-formidable': specifier: ^1 version: 1.2.3 - '@types/express-session': - specifier: ^1 - version: 1.18.0 '@types/file-saver': specifier: ^2.0.5 version: 2.0.7 @@ -434,23 +425,7 @@ importers: specifier: ^0.11.7 version: 0.11.7(vite@4.5.5(@types/node@16.18.119)(terser@5.36.0)) - packages/config: - dependencies: - eslint-config-prettier: - specifier: ^8.3.0 - version: 8.10.0(eslint@8.57.1) - eslint-config-turbo: - specifier: latest - version: 2.2.3(eslint@8.57.1) - eslint-plugin-react: - specifier: 7.33.2 - version: 7.33.2(eslint@8.57.1) - devDependencies: - typescript: - specifier: ^5.6.2 - version: 5.6.3 - - packages/passport: + packages/auth: dependencies: '@celluloid/prisma': specifier: workspace:* @@ -458,27 +433,24 @@ importers: '@celluloid/utils': specifier: workspace:* version: link:../utils + '@prisma/client': + specifier: ^5.22.0 + version: 5.22.0(prisma@5.22.0) + '@t3-oss/env-core': + specifier: ^0.11.1 + version: 0.11.1(typescript@5.6.3)(zod@3.23.8) bcryptjs: specifier: ^2.4.3 version: 2.4.3 - connect-redis: - specifier: ^7.1.0 - version: 7.1.1(express-session@1.18.1) + better-auth: + specifier: ^1.0.3 + version: 1.0.3(encoding@0.1.13) express: specifier: ^4.19.2 version: 4.21.1 - express-session: - specifier: ^1.17.3 - version: 1.18.1 - passport: - specifier: ^0.6.0 - version: 0.6.0 - passport-local: - specifier: ^1.0.0 - version: 1.0.0 - redis: - specifier: ^4.6.10 - version: 4.7.0 + zod: + specifier: ^3.23.8 + version: 3.23.8 devDependencies: '@celluloid/config': specifier: workspace:* @@ -489,18 +461,9 @@ importers: '@types/express': specifier: ^5.0.0 version: 5.0.0 - '@types/express-session': - specifier: ^1.17.8 - version: 1.18.0 '@types/node': specifier: ^18.14.2 version: 18.19.64 - '@types/passport': - specifier: ^1.0.14 - version: 1.0.17 - '@types/passport-local': - specifier: ^1.0.37 - version: 1.0.38 tsup: specifier: ^8.3.0 version: 8.3.5(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) @@ -508,6 +471,22 @@ importers: specifier: ^5.6.2 version: 5.6.3 + packages/config: + dependencies: + eslint-config-prettier: + specifier: ^8.3.0 + version: 8.10.0(eslint@8.57.1) + eslint-config-turbo: + specifier: latest + version: 2.3.1(eslint@8.57.1) + eslint-plugin-react: + specifier: 7.33.2 + version: 7.33.2(eslint@8.57.1) + devDependencies: + typescript: + specifier: ^5.6.2 + version: 5.6.3 + packages/prisma: dependencies: '@prisma/client': @@ -599,9 +578,9 @@ importers: packages/trpc: dependencies: - '@celluloid/passport': + '@celluloid/auth': specifier: workspace:* - version: link:../passport + version: link:../auth '@celluloid/prisma': specifier: workspace:* version: link:../prisma @@ -620,6 +599,9 @@ importers: bcryptjs: specifier: ^2.4.3 version: 2.4.3 + better-auth: + specifier: ^1.0.3 + version: 1.0.3(encoding@0.1.13) change-case: specifier: ^4.1.2 version: 4.1.2 @@ -629,9 +611,6 @@ importers: express: specifier: ^4.19.2 version: 4.21.1 - express-session: - specifier: ^1.17.3 - version: 1.18.1 js2xmlparser: specifier: ^5.0.0 version: 5.0.0 @@ -672,9 +651,6 @@ importers: '@types/express': specifier: ^5.0.0 version: 5.0.0 - '@types/express-session': - specifier: ^1.17.8 - version: 1.18.0 '@types/mjml': specifier: ^4.7.3 version: 4.7.4 @@ -1528,6 +1504,9 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@better-fetch/fetch@1.1.12': + resolution: {integrity: sha512-B3bfloI/2UBQWIATRN6qmlORrvx3Mp0kkNjmXLv0b+DtbtR+pP4/I5kQA/rDUv+OReLywCCldf6co4LdDmh8JA==} + '@celluloid/react-player@2.14.0': resolution: {integrity: sha512-f+VTDC8MozDgPI+y2MvDn+FnEW2yX+brURdpMvjzLSA8D2OO5xV5JNGwFj/b2rzfdEZUcXxcSSxHogiyF9AIpg==} peerDependencies: @@ -1710,6 +1689,12 @@ packages: resize-observer-polyfill: '>= ^1.5.x' use-debounce: '>= 10.0.1' + '@emnapi/core@0.45.0': + resolution: {integrity: sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==} + + '@emnapi/runtime@0.45.0': + resolution: {integrity: sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==} + '@emotion/babel-plugin@11.12.0': resolution: {integrity: sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==} @@ -2238,6 +2223,9 @@ packages: react: ^16.8.5 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.5 || ^17.0.0 || ^18.0.0 + '@hexagon/base64@1.1.28': + resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==} + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -2370,6 +2358,9 @@ packages: '@leichtgewicht/ip-codec@2.0.5': resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} + '@levischuck/tiny-cbor@0.2.2': + resolution: {integrity: sha512-f5CnPw997Y2GQ8FAvtuVVC19FX8mwNNC+1XJcIi16n/LTJifKO6QBgGLgN3YEmqtGMk17SKSuoWES3imJVxAVw==} + '@material-ui/core@3.9.4': resolution: {integrity: sha512-r8QFLSexcYZbnqy/Hn4v8xzmAJV41yaodUVjmbGLi1iGDLG3+W941hEtEiBmxTRRqv2BdK3r4ijILcqKmDv/Sw==} engines: {node: '>=6.0.0'} @@ -2584,6 +2575,187 @@ packages: '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} + '@noble/ciphers@0.6.0': + resolution: {integrity: sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==} + + '@noble/hashes@1.6.1': + resolution: {integrity: sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==} + engines: {node: ^14.21.3 || >=16} + + '@node-rs/argon2-android-arm-eabi@1.7.0': + resolution: {integrity: sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@node-rs/argon2-android-arm64@1.7.0': + resolution: {integrity: sha512-s9j/G30xKUx8WU50WIhF0fIl1EdhBGq0RQ06lEhZ0Gi0ap8lhqbE2Bn5h3/G2D1k0Dx+yjeVVNmt/xOQIRG38A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@node-rs/argon2-darwin-arm64@1.7.0': + resolution: {integrity: sha512-ZIz4L6HGOB9U1kW23g+m7anGNuTZ0RuTw0vNp3o+2DWpb8u8rODq6A8tH4JRL79S+Co/Nq608m9uackN2pe0Rw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@node-rs/argon2-darwin-x64@1.7.0': + resolution: {integrity: sha512-5oi/pxqVhODW/pj1+3zElMTn/YukQeywPHHYDbcAW3KsojFjKySfhcJMd1DjKTc+CHQI+4lOxZzSUzK7mI14Hw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@node-rs/argon2-freebsd-x64@1.7.0': + resolution: {integrity: sha512-Ify08683hA4QVXYoIm5SUWOY5DPIT/CMB0CQT+IdxQAg/F+qp342+lUkeAtD5bvStQuCx/dFO3bnnzoe2clMhA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@node-rs/argon2-linux-arm-gnueabihf@1.7.0': + resolution: {integrity: sha512-7DjDZ1h5AUHAtRNjD19RnQatbhL+uuxBASuuXIBu4/w6Dx8n7YPxwTP4MXfsvuRgKuMWiOb/Ub/HJ3kXVCXRkg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@node-rs/argon2-linux-arm64-gnu@1.7.0': + resolution: {integrity: sha512-nJDoMP4Y3YcqGswE4DvP080w6O24RmnFEDnL0emdI8Nou17kNYBzP2546Nasx9GCyLzRcYQwZOUjrtUuQ+od2g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@node-rs/argon2-linux-arm64-musl@1.7.0': + resolution: {integrity: sha512-BKWS8iVconhE3jrb9mj6t1J9vwUqQPpzCbUKxfTGJfc+kNL58F1SXHBoe2cDYGnHrFEHTY0YochzXoAfm4Dm/A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@node-rs/argon2-linux-x64-gnu@1.7.0': + resolution: {integrity: sha512-EmgqZOlf4Jurk/szW1iTsVISx25bKksVC5uttJDUloTgsAgIGReCpUUO1R24pBhu9ESJa47iv8NSf3yAfGv6jQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@node-rs/argon2-linux-x64-musl@1.7.0': + resolution: {integrity: sha512-/o1efYCYIxjfuoRYyBTi2Iy+1iFfhqHCvvVsnjNSgO1xWiWrX0Rrt/xXW5Zsl7vS2Y+yu8PL8KFWRzZhaVxfKA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@node-rs/argon2-wasm32-wasi@1.7.0': + resolution: {integrity: sha512-Evmk9VcxqnuwQftfAfYEr6YZYSPLzmKUsbFIMep5nTt9PT4XYRFAERj7wNYp+rOcBenF3X4xoB+LhwcOMTNE5w==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@node-rs/argon2-win32-arm64-msvc@1.7.0': + resolution: {integrity: sha512-qgsU7T004COWWpSA0tppDqDxbPLgg8FaU09krIJ7FBl71Sz8SFO40h7fDIjfbTT5w7u6mcaINMQ5bSHu75PCaA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@node-rs/argon2-win32-ia32-msvc@1.7.0': + resolution: {integrity: sha512-JGafwWYQ/HpZ3XSwP4adQ6W41pRvhcdXvpzIWtKvX+17+xEXAe2nmGWM6s27pVkg1iV2ZtoYLRDkOUoGqZkCcg==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@node-rs/argon2-win32-x64-msvc@1.7.0': + resolution: {integrity: sha512-9oq4ShyFakw8AG3mRls0AoCpxBFcimYx7+jvXeAf2OqKNO+mSA6eZ9z7KQeVCi0+SOEUYxMGf5UiGiDb9R6+9Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@node-rs/argon2@1.7.0': + resolution: {integrity: sha512-zfULc+/tmcWcxn+nHkbyY8vP3+MpEqKORbszt4UkpqZgBgDAAIYvuDN/zukfTgdmo6tmJKKVfzigZOPk4LlIog==} + engines: {node: '>= 10'} + + '@node-rs/bcrypt-android-arm-eabi@1.9.0': + resolution: {integrity: sha512-nOCFISGtnodGHNiLrG0WYLWr81qQzZKYfmwHc7muUeq+KY0sQXyHOwZk9OuNQAWv/lnntmtbwkwT0QNEmOyLvA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@node-rs/bcrypt-android-arm64@1.9.0': + resolution: {integrity: sha512-+ZrIAtigVmjYkqZQTThHVlz0+TG6D+GDHWhVKvR2DifjtqJ0i+mb9gjo++hN+fWEQdWNGxKCiBBjwgT4EcXd6A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@node-rs/bcrypt-darwin-arm64@1.9.0': + resolution: {integrity: sha512-CQiS+F9Pa0XozvkXR1g7uXE9QvBOPOplDg0iCCPRYTN9PqA5qYxhwe48G3o+v2UeQceNRrbnEtWuANm7JRqIhw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@node-rs/bcrypt-darwin-x64@1.9.0': + resolution: {integrity: sha512-4pTKGawYd7sNEjdJ7R/R67uwQH1VvwPZ0SSUMmeNHbxD5QlwAPXdDH11q22uzVXsvNFZ6nGQBg8No5OUGpx6Ug==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@node-rs/bcrypt-freebsd-x64@1.9.0': + resolution: {integrity: sha512-UmWzySX4BJhT/B8xmTru6iFif3h0Rpx3TqxRLCcbgmH43r7k5/9QuhpiyzpvKGpKHJCFNm4F3rC2wghvw5FCIg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@node-rs/bcrypt-linux-arm-gnueabihf@1.9.0': + resolution: {integrity: sha512-8qoX4PgBND2cVwsbajoAWo3NwdfJPEXgpCsZQZURz42oMjbGyhhSYbovBCskGU3EBLoC8RA2B1jFWooeYVn5BA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@node-rs/bcrypt-linux-arm64-gnu@1.9.0': + resolution: {integrity: sha512-TuAC6kx0SbcIA4mSEWPi+OCcDjTQUMl213v5gMNlttF+D4ieIZx6pPDGTaMO6M2PDHTeCG0CBzZl0Lu+9b0c7Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@node-rs/bcrypt-linux-arm64-musl@1.9.0': + resolution: {integrity: sha512-/sIvKDABOI8QOEnLD7hIj02BVaNOuCIWBKvxcJOt8+TuwJ6zmY1UI5kSv9d99WbiHjTp97wtAUbZQwauU4b9ew==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@node-rs/bcrypt-linux-x64-gnu@1.9.0': + resolution: {integrity: sha512-DyyhDHDsLBsCKz1tZ1hLvUZSc1DK0FU0v52jK6IBQxrj24WscSU9zZe7ie/V9kdmA4Ep57BfpWX8Dsa2JxGdgQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@node-rs/bcrypt-linux-x64-musl@1.9.0': + resolution: {integrity: sha512-duIiuqQ+Lew8ASSAYm6ZRqcmfBGWwsi81XLUwz86a2HR7Qv6V4yc3ZAUQovAikhjCsIqe8C11JlAZSK6+PlXYg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@node-rs/bcrypt-wasm32-wasi@1.9.0': + resolution: {integrity: sha512-ylaGmn9Wjwv/D5lxtawttx3H6Uu2WTTR7lWlRHGT6Ga/MB1Vj4OjSGUW8G8zIVnKuXpGbZ92pgHlt4HUpSLctw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@node-rs/bcrypt-win32-arm64-msvc@1.9.0': + resolution: {integrity: sha512-2h86gF7QFyEzODuDFml/Dp1MSJoZjxJ4yyT2Erf4NkwsiA5MqowUhUsorRwZhX6+2CtlGa7orbwi13AKMsYndw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@node-rs/bcrypt-win32-ia32-msvc@1.9.0': + resolution: {integrity: sha512-kqxalCvhs4FkN0+gWWfa4Bdy2NQAkfiqq/CEf6mNXC13RSV673Ev9V8sRlQyNpCHCNkeXfOT9pgoBdJmMs9muA==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@node-rs/bcrypt-win32-x64-msvc@1.9.0': + resolution: {integrity: sha512-2y0Tuo6ZAT2Cz8V7DHulSlv1Bip3zbzeXyeur+uR25IRNYXKvI/P99Zl85Fbuu/zzYAZRLLlGTRe6/9IHofe/w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@node-rs/bcrypt@1.9.0': + resolution: {integrity: sha512-u2OlIxW264bFUfvbFqDz9HZKFjwe8FHFtn7T/U8mYjPZ7DWYpbUB+/dkW/QgYfMSfR0ejkyuWaBBe0coW7/7ig==} + engines: {node: '>= 10'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2607,6 +2779,21 @@ packages: '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@peculiar/asn1-android@2.3.13': + resolution: {integrity: sha512-0VTNazDGKrLS6a3BwTDZanqq6DR/I3SbvmDMuS8Be+OYpvM6x1SRDh9AGDsHVnaCOIztOspCPc6N1m+iUv1Xxw==} + + '@peculiar/asn1-ecc@2.3.14': + resolution: {integrity: sha512-zWPyI7QZto6rnLv6zPniTqbGaLh6zBpJyI46r1yS/bVHJXT2amdMHCRRnbV5yst2H8+ppXG6uXu/M6lKakiQ8w==} + + '@peculiar/asn1-rsa@2.3.13': + resolution: {integrity: sha512-wBNQqCyRtmqvXkGkL4DR3WxZhHy8fDiYtOjTeCd7SFE5F6GBeafw3EJ94PX/V0OJJrjQ40SkRY2IZu3ZSyBqcg==} + + '@peculiar/asn1-schema@2.3.13': + resolution: {integrity: sha512-3Xq3a01WkHRZL8X04Zsfg//mGaA21xlL4tlVn4v2xGT0JStiztATRkMwa5b+f/HXmY2smsiLXYK46Gwgzvfg3g==} + + '@peculiar/asn1-x509@2.3.13': + resolution: {integrity: sha512-PfeLQl2skXmxX2/AFFCVaWU8U6FKW1Db43mgBhShCOFS1bVxqtvusq1hVjfuEcuSQGedrLdCSvTgabluwN/M9A==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -2964,6 +3151,16 @@ packages: '@sideway/pinpoint@2.0.0': resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + '@simplewebauthn/browser@10.0.0': + resolution: {integrity: sha512-hG0JMZD+LiLUbpQcAjS4d+t4gbprE/dLYop/CkE01ugU/9sKXflxV5s0DRjdz3uNMFecatRfb4ZLG3XvF8m5zg==} + + '@simplewebauthn/server@10.0.1': + resolution: {integrity: sha512-djNWcRn+H+6zvihBFJSpG3fzb0NQS9c/Mw5dYOtZ9H+oDw8qn9Htqxt4cpqRvSOAfwqP7rOvE9rwqVaoGGc3hg==} + engines: {node: '>=20.0.0'} + + '@simplewebauthn/types@10.0.0': + resolution: {integrity: sha512-SFXke7xkgPRowY2E+8djKbdEznTVnD5R6GO7GPTthpHrokLvNKw8C3lFZypTxLI7KkCfGPfhtqB3d7OVGGa9jQ==} + '@sinclair/typebox@0.24.51': resolution: {integrity: sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==} @@ -3359,6 +3556,9 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@tybys/wasm-util@0.8.3': + resolution: {integrity: sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==} + '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -3422,9 +3622,6 @@ packages: '@types/express-serve-static-core@5.0.1': resolution: {integrity: sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==} - '@types/express-session@1.18.0': - resolution: {integrity: sha512-27JdDRgor6PoYlURY+Y5kCakqp5ulC0kmf7y+QwaY+hv9jEFuQOThgkjyA53RP3jmKuBsH5GR6qEfFmvb8mwOA==} - '@types/express@4.17.21': resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} @@ -3559,15 +3756,6 @@ packages: '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - '@types/passport-local@1.0.38': - resolution: {integrity: sha512-nsrW4A963lYE7lNTv9cr5WmiUD1ibYJvWrpE13oxApFsRt77b0RdtZvKbCdNIY4v/QZ6TRQWaDDEwV1kCTmcXg==} - - '@types/passport-strategy@0.2.38': - resolution: {integrity: sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==} - - '@types/passport@1.0.17': - resolution: {integrity: sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==} - '@types/prettier@2.7.3': resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} @@ -4074,6 +4262,10 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + asn1js@3.0.5: + resolution: {integrity: sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==} + engines: {node: '>=12.0.0'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -4206,6 +4398,12 @@ packages: bcryptjs@2.4.3: resolution: {integrity: sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==} + better-auth@1.0.3: + resolution: {integrity: sha512-zlKFfm91068B+O4xGiwDDomM/0t9RBSSaal+/OgONMrg99KWZPQFFTk4gJ8vEdoAn9yidPlwYcnfDa19CshEBQ==} + + better-call@0.3.2: + resolution: {integrity: sha512-ZYUADh4S4JF3QOIgC0QAm4N9Ifb7v3rOEcwkIxr3UXxd+GCrGmEzv7VDe35o1ldX0Zcb9CX3QfT0jdJoMIKA9w==} + bfj@7.1.0: resolution: {integrity: sha512-I6MMLkn+anzNdCUp9hMRyui1HaNEUCco50lxbvNS4+EyXg8lN3nJ48PjPWtbH8UVS9CuMoaKE9U2V3l29DaRQw==} engines: {node: '>= 8.0.0'} @@ -4627,12 +4825,6 @@ packages: resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==} engines: {node: '>=0.8'} - connect-redis@7.1.1: - resolution: {integrity: sha512-M+z7alnCJiuzKa8/1qAYdGUXHYfDnLolOGAUjOioB07pP39qxjG+X9ibsud7qUBc4jMV5Mcy3ugGv8eFcgamJQ==} - engines: {node: '>=16'} - peerDependencies: - express-session: '>=1' - consola@3.2.3: resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} engines: {node: ^14.18.0 || >=16.10.0} @@ -5420,8 +5612,8 @@ packages: eslint-config-react@1.1.7: resolution: {integrity: sha512-P4Z6u68wf0BvIvZNu+U8uQsk3DcZ1CcCI1XpUkJlG6vOa+iVcSQLgE01f2DB2kXlKRcT8/3dsH+wveLgvEgbkQ==} - eslint-config-turbo@2.2.3: - resolution: {integrity: sha512-/zwNU+G2w0HszXzWILdl6/Catt86ejUG7vsFSdpnFzFAAUbbT2TxgoCFvC1fKtm6+SkQsXwkRRe9tFz0aMftpg==} + eslint-config-turbo@2.3.1: + resolution: {integrity: sha512-pxxCLLgnZYCjJoGrzUu3jAcb67bKVykLblyMtgTzHN7DlNu6tnp89K3/5fznc6ALyXwXFp0K+nM+Sxst43oaoA==} peerDependencies: eslint: '>6.6.0' @@ -5538,8 +5730,8 @@ packages: peerDependencies: eslint: ^7.5.0 || ^8.0.0 - eslint-plugin-turbo@2.2.3: - resolution: {integrity: sha512-LHt35VwxthdGVO6hQRfvmFb6ee8/exAzAYWCy4o87Bnp7urltP8qg7xMd4dPSLAhtfnI2xSo1WgeVaR3MeItxw==} + eslint-plugin-turbo@2.3.1: + resolution: {integrity: sha512-M5MBYBkcQsv11MFHJ+6WpzLpiTBx0OApeUMAHlO4L0eHqQxY03GrmHXjXfozqB+9HwGrW9fqihBzVRllyixJDA==} peerDependencies: eslint: '>6.6.0' @@ -6811,6 +7003,9 @@ packages: joi@17.13.3: resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + jose@5.9.6: + resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==} + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -6987,6 +7182,10 @@ packages: resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} engines: {node: '>= 8'} + kysely@0.27.4: + resolution: {integrity: sha512-dyNKv2KRvYOQPLCAOCjjQuCk4YFd33BvGdf/o5bC7FiW+BB6snA81Zt+2wT9QDFzKqxKa5rrOmvlK/anehCcgA==} + engines: {node: '>=14.0.0'} + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -7262,6 +7461,9 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} + memfs-browser@3.5.10302: + resolution: {integrity: sha512-JJTc/nh3ig05O0gBBGZjTCPOyydaTxNF0uHYBrcc1gHNnO+KIHIvo0Y1FKCJsaei6FCl8C6xfQomXqu+cuzkIw==} + memfs@3.5.3: resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} engines: {node: '>= 4.0.0'} @@ -7593,6 +7795,15 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@5.0.8: + resolution: {integrity: sha512-TcJPw+9RV9dibz1hHUzlLVy8N4X9TnwirAjrU08Juo6BNKggzVfP2ZJ/3ZUSq15Xl5i85i+Z89XBO90pB2PghQ==} + engines: {node: ^18 || >=20} + hasBin: true + + nanostores@0.11.3: + resolution: {integrity: sha512-TUes3xKIX33re4QzdxwZ6tdbodjmn3tWXCEc1uokiEmo14sI1EaGYNs2k3bU2pyyGNmBqFGAVl6jAGWd06AVIg==} + engines: {node: ^18.0.0 || >=20.0.0} + natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} @@ -7845,6 +8056,9 @@ packages: orderedmap@2.1.1: resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + oslo@1.2.1: + resolution: {integrity: sha512-HfIhB5ruTdQv0XX2XlncWQiJ5SIHZ7NHZhVyHth0CSZ/xzge00etRyYy/3wp/Dsu+PkxMC+6+B2lS/GcKoewkA==} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -7927,18 +8141,6 @@ packages: pascal-case@3.1.2: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} - passport-local@1.0.0: - resolution: {integrity: sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==} - engines: {node: '>= 0.4.0'} - - passport-strategy@1.0.0: - resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==} - engines: {node: '>= 0.4.0'} - - passport@0.6.0: - resolution: {integrity: sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==} - engines: {node: '>= 0.4.0'} - path-case@3.0.4: resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==} @@ -7988,9 +8190,6 @@ packages: pause-stream@0.0.11: resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} - pause@0.0.1: - resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} - performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} @@ -8709,6 +8908,13 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pvtsutils@1.3.6: + resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} + + pvutils@1.1.3: + resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==} + engines: {node: '>=6.0.0'} + q@1.5.1: resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} engines: {node: '>=0.6.0', teleport: '>=0.2.0'} @@ -9257,6 +9463,9 @@ packages: rope-sequence@1.3.4: resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + rou3@0.5.1: + resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==} + rst-selector-parser@2.2.3: resolution: {integrity: sha512-nDG1rZeP6oFTLN6yNDV/uiAvs1+FS/KlrEwh7+y7dpuApDBy6bI2HTBcc0/V8lv9OTqfyD34eF7au2pm8aBbhA==} @@ -9395,6 +9604,9 @@ packages: engines: {node: '>= 14'} hasBin: true + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -11797,6 +12009,8 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@better-fetch/fetch@1.1.12': {} + '@celluloid/react-player@2.14.0(react@18.3.1)': dependencies: deepmerge: 4.3.1 @@ -12016,6 +12230,16 @@ snapshots: resize-observer-polyfill: 1.5.1 use-debounce: 10.0.4(react@18.3.1) + '@emnapi/core@0.45.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@0.45.0': + dependencies: + tslib: 2.8.1 + optional: true + '@emotion/babel-plugin@11.12.0': dependencies: '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) @@ -12377,6 +12601,8 @@ snapshots: - '@types/react-dom' - react-native + '@hexagon/base64@1.1.28': {} + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -12427,7 +12653,7 @@ snapshots: '@jest/console@28.1.3': dependencies: '@jest/types': 28.1.3 - '@types/node': 16.18.119 + '@types/node': 18.19.64 chalk: 4.1.2 jest-message-util: 28.1.3 jest-util: 28.1.3 @@ -12633,7 +12859,7 @@ snapshots: '@jest/schemas': 28.1.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 16.18.119 + '@types/node': 18.19.64 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -12666,6 +12892,8 @@ snapshots: '@leichtgewicht/ip-codec@2.0.5': {} + '@levischuck/tiny-cbor@0.2.2': {} + '@material-ui/core@3.9.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 @@ -12917,6 +13145,138 @@ snapshots: dependencies: eslint-scope: 5.1.1 + '@noble/ciphers@0.6.0': {} + + '@noble/hashes@1.6.1': {} + + '@node-rs/argon2-android-arm-eabi@1.7.0': + optional: true + + '@node-rs/argon2-android-arm64@1.7.0': + optional: true + + '@node-rs/argon2-darwin-arm64@1.7.0': + optional: true + + '@node-rs/argon2-darwin-x64@1.7.0': + optional: true + + '@node-rs/argon2-freebsd-x64@1.7.0': + optional: true + + '@node-rs/argon2-linux-arm-gnueabihf@1.7.0': + optional: true + + '@node-rs/argon2-linux-arm64-gnu@1.7.0': + optional: true + + '@node-rs/argon2-linux-arm64-musl@1.7.0': + optional: true + + '@node-rs/argon2-linux-x64-gnu@1.7.0': + optional: true + + '@node-rs/argon2-linux-x64-musl@1.7.0': + optional: true + + '@node-rs/argon2-wasm32-wasi@1.7.0': + dependencies: + '@emnapi/core': 0.45.0 + '@emnapi/runtime': 0.45.0 + '@tybys/wasm-util': 0.8.3 + memfs-browser: 3.5.10302 + optional: true + + '@node-rs/argon2-win32-arm64-msvc@1.7.0': + optional: true + + '@node-rs/argon2-win32-ia32-msvc@1.7.0': + optional: true + + '@node-rs/argon2-win32-x64-msvc@1.7.0': + optional: true + + '@node-rs/argon2@1.7.0': + optionalDependencies: + '@node-rs/argon2-android-arm-eabi': 1.7.0 + '@node-rs/argon2-android-arm64': 1.7.0 + '@node-rs/argon2-darwin-arm64': 1.7.0 + '@node-rs/argon2-darwin-x64': 1.7.0 + '@node-rs/argon2-freebsd-x64': 1.7.0 + '@node-rs/argon2-linux-arm-gnueabihf': 1.7.0 + '@node-rs/argon2-linux-arm64-gnu': 1.7.0 + '@node-rs/argon2-linux-arm64-musl': 1.7.0 + '@node-rs/argon2-linux-x64-gnu': 1.7.0 + '@node-rs/argon2-linux-x64-musl': 1.7.0 + '@node-rs/argon2-wasm32-wasi': 1.7.0 + '@node-rs/argon2-win32-arm64-msvc': 1.7.0 + '@node-rs/argon2-win32-ia32-msvc': 1.7.0 + '@node-rs/argon2-win32-x64-msvc': 1.7.0 + + '@node-rs/bcrypt-android-arm-eabi@1.9.0': + optional: true + + '@node-rs/bcrypt-android-arm64@1.9.0': + optional: true + + '@node-rs/bcrypt-darwin-arm64@1.9.0': + optional: true + + '@node-rs/bcrypt-darwin-x64@1.9.0': + optional: true + + '@node-rs/bcrypt-freebsd-x64@1.9.0': + optional: true + + '@node-rs/bcrypt-linux-arm-gnueabihf@1.9.0': + optional: true + + '@node-rs/bcrypt-linux-arm64-gnu@1.9.0': + optional: true + + '@node-rs/bcrypt-linux-arm64-musl@1.9.0': + optional: true + + '@node-rs/bcrypt-linux-x64-gnu@1.9.0': + optional: true + + '@node-rs/bcrypt-linux-x64-musl@1.9.0': + optional: true + + '@node-rs/bcrypt-wasm32-wasi@1.9.0': + dependencies: + '@emnapi/core': 0.45.0 + '@emnapi/runtime': 0.45.0 + '@tybys/wasm-util': 0.8.3 + memfs-browser: 3.5.10302 + optional: true + + '@node-rs/bcrypt-win32-arm64-msvc@1.9.0': + optional: true + + '@node-rs/bcrypt-win32-ia32-msvc@1.9.0': + optional: true + + '@node-rs/bcrypt-win32-x64-msvc@1.9.0': + optional: true + + '@node-rs/bcrypt@1.9.0': + optionalDependencies: + '@node-rs/bcrypt-android-arm-eabi': 1.9.0 + '@node-rs/bcrypt-android-arm64': 1.9.0 + '@node-rs/bcrypt-darwin-arm64': 1.9.0 + '@node-rs/bcrypt-darwin-x64': 1.9.0 + '@node-rs/bcrypt-freebsd-x64': 1.9.0 + '@node-rs/bcrypt-linux-arm-gnueabihf': 1.9.0 + '@node-rs/bcrypt-linux-arm64-gnu': 1.9.0 + '@node-rs/bcrypt-linux-arm64-musl': 1.9.0 + '@node-rs/bcrypt-linux-x64-gnu': 1.9.0 + '@node-rs/bcrypt-linux-x64-musl': 1.9.0 + '@node-rs/bcrypt-wasm32-wasi': 1.9.0 + '@node-rs/bcrypt-win32-arm64-msvc': 1.9.0 + '@node-rs/bcrypt-win32-ia32-msvc': 1.9.0 + '@node-rs/bcrypt-win32-x64-msvc': 1.9.0 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -12945,6 +13305,40 @@ snapshots: '@one-ini/wasm@0.1.1': {} + '@peculiar/asn1-android@2.3.13': + dependencies: + '@peculiar/asn1-schema': 2.3.13 + asn1js: 3.0.5 + tslib: 2.8.1 + + '@peculiar/asn1-ecc@2.3.14': + dependencies: + '@peculiar/asn1-schema': 2.3.13 + '@peculiar/asn1-x509': 2.3.13 + asn1js: 3.0.5 + tslib: 2.8.1 + + '@peculiar/asn1-rsa@2.3.13': + dependencies: + '@peculiar/asn1-schema': 2.3.13 + '@peculiar/asn1-x509': 2.3.13 + asn1js: 3.0.5 + tslib: 2.8.1 + + '@peculiar/asn1-schema@2.3.13': + dependencies: + asn1js: 3.0.5 + pvtsutils: 1.3.6 + tslib: 2.8.1 + + '@peculiar/asn1-x509@2.3.13': + dependencies: + '@peculiar/asn1-schema': 2.3.13 + asn1js: 3.0.5 + ipaddr.js: 2.2.0 + pvtsutils: 1.3.6 + tslib: 2.8.1 + '@pkgjs/parseargs@0.11.0': optional: true @@ -13316,6 +13710,26 @@ snapshots: '@sideway/pinpoint@2.0.0': {} + '@simplewebauthn/browser@10.0.0': + dependencies: + '@simplewebauthn/types': 10.0.0 + + '@simplewebauthn/server@10.0.1(encoding@0.1.13)': + dependencies: + '@hexagon/base64': 1.1.28 + '@levischuck/tiny-cbor': 0.2.2 + '@peculiar/asn1-android': 2.3.13 + '@peculiar/asn1-ecc': 2.3.14 + '@peculiar/asn1-rsa': 2.3.13 + '@peculiar/asn1-schema': 2.3.13 + '@peculiar/asn1-x509': 2.3.13 + '@simplewebauthn/types': 10.0.0 + cross-fetch: 4.0.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding + + '@simplewebauthn/types@10.0.0': {} + '@sinclair/typebox@0.24.51': {} '@sinonjs/commons@1.8.6': @@ -13743,6 +14157,11 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@tybys/wasm-util@0.8.3': + dependencies: + tslib: 2.8.1 + optional: true + '@types/aria-query@5.0.4': {} '@types/babel__core@7.20.5': @@ -13771,20 +14190,20 @@ snapshots: '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 16.18.119 + '@types/node': 18.19.64 '@types/bonjour@3.5.13': dependencies: - '@types/node': 16.18.119 + '@types/node': 18.19.64 '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 5.0.1 - '@types/node': 16.18.119 + '@types/node': 18.19.64 '@types/connect@3.4.38': dependencies: - '@types/node': 16.18.119 + '@types/node': 18.19.64 '@types/cookie-parser@1.4.7': dependencies: @@ -13824,7 +14243,7 @@ snapshots: '@types/express-serve-static-core@4.19.6': dependencies: - '@types/node': 16.18.119 + '@types/node': 18.19.64 '@types/qs': 6.9.17 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -13836,10 +14255,6 @@ snapshots: '@types/range-parser': 1.2.7 '@types/send': 0.17.4 - '@types/express-session@1.18.0': - dependencies: - '@types/express': 4.17.21 - '@types/express@4.17.21': dependencies: '@types/body-parser': 1.19.5 @@ -13862,7 +14277,7 @@ snapshots: '@types/formidable@3.4.5': dependencies: - '@types/node': 16.18.119 + '@types/node': 18.19.64 '@types/get-urls@9.1.3': dependencies: @@ -13891,7 +14306,7 @@ snapshots: '@types/http-proxy@1.17.15': dependencies: - '@types/node': 16.18.119 + '@types/node': 18.19.64 '@types/i18next-browser-languagedetector@3.0.0': dependencies: @@ -13970,7 +14385,7 @@ snapshots: '@types/node-forge@1.3.11': dependencies: - '@types/node': 16.18.119 + '@types/node': 18.19.64 '@types/node@16.18.119': {} @@ -13992,21 +14407,6 @@ snapshots: '@types/parse-json@4.0.2': {} - '@types/passport-local@1.0.38': - dependencies: - '@types/express': 5.0.0 - '@types/passport': 1.0.17 - '@types/passport-strategy': 0.2.38 - - '@types/passport-strategy@0.2.38': - dependencies: - '@types/express': 5.0.0 - '@types/passport': 1.0.17 - - '@types/passport@1.0.17': - dependencies: - '@types/express': 5.0.0 - '@types/prettier@2.7.3': {} '@types/prop-types@15.7.13': {} @@ -14061,7 +14461,7 @@ snapshots: '@types/resolve@1.17.1': dependencies: - '@types/node': 16.18.119 + '@types/node': 18.19.64 '@types/resolve@1.20.2': {} @@ -14072,21 +14472,21 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 16.18.119 + '@types/node': 18.19.64 '@types/serve-index@1.9.4': dependencies: - '@types/express': 4.17.21 + '@types/express': 5.0.0 '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/node': 16.18.119 + '@types/node': 18.19.64 '@types/send': 0.17.4 '@types/sockjs@0.3.36': dependencies: - '@types/node': 16.18.119 + '@types/node': 18.19.64 '@types/stack-utils@2.0.3': {} @@ -14104,7 +14504,7 @@ snapshots: '@types/ws@8.5.13': dependencies: - '@types/node': 16.18.119 + '@types/node': 18.19.64 '@types/yargs-parser@21.0.3': {} @@ -14654,6 +15054,12 @@ snapshots: asap@2.0.6: {} + asn1js@3.0.5: + dependencies: + pvtsutils: 1.3.6 + pvutils: 1.1.3 + tslib: 2.8.1 + ast-types-flow@0.0.8: {} ast-types@0.13.4: @@ -14845,6 +15251,34 @@ snapshots: bcryptjs@2.4.3: {} + better-auth@1.0.3(encoding@0.1.13): + dependencies: + '@better-fetch/fetch': 1.1.12 + '@noble/ciphers': 0.6.0 + '@noble/hashes': 1.6.1 + '@simplewebauthn/browser': 10.0.0 + '@simplewebauthn/server': 10.0.1(encoding@0.1.13) + better-call: 0.3.2 + consola: 3.2.3 + defu: 6.1.4 + jose: 5.9.6 + kysely: 0.27.4 + nanoid: 5.0.8 + nanostores: 0.11.3 + oslo: 1.2.1 + uncrypto: 0.1.3 + zod: 3.23.8 + transitivePeerDependencies: + - encoding + + better-call@0.3.2: + dependencies: + '@better-fetch/fetch': 1.1.12 + rou3: 0.5.1 + set-cookie-parser: 2.7.1 + uncrypto: 0.1.3 + zod: 3.23.8 + bfj@7.1.0: dependencies: bluebird: 3.7.2 @@ -15315,10 +15749,6 @@ snapshots: connect-history-api-fallback@2.0.0: {} - connect-redis@7.1.1(express-session@1.18.1): - dependencies: - express-session: 1.18.1 - consola@3.2.3: {} constant-case@3.0.4: @@ -16251,10 +16681,10 @@ snapshots: eslint-config-react@1.1.7: {} - eslint-config-turbo@2.2.3(eslint@8.57.1): + eslint-config-turbo@2.3.1(eslint@8.57.1): dependencies: eslint: 8.57.1 - eslint-plugin-turbo: 2.2.3(eslint@8.57.1) + eslint-plugin-turbo: 2.3.1(eslint@8.57.1) eslint-import-resolver-node@0.3.9: dependencies: @@ -16400,7 +16830,7 @@ snapshots: - supports-color - typescript - eslint-plugin-turbo@2.2.3(eslint@8.57.1): + eslint-plugin-turbo@2.3.1(eslint@8.57.1): dependencies: dotenv: 16.0.3 eslint: 8.57.1 @@ -18002,7 +18432,7 @@ snapshots: jest-util@28.1.3: dependencies: '@jest/types': 28.1.3 - '@types/node': 16.18.119 + '@types/node': 18.19.64 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -18042,7 +18472,7 @@ snapshots: dependencies: '@jest/test-result': 28.1.3 '@jest/types': 28.1.3 - '@types/node': 16.18.119 + '@types/node': 18.19.64 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.10.2 @@ -18051,19 +18481,19 @@ snapshots: jest-worker@26.6.2: dependencies: - '@types/node': 16.18.119 + '@types/node': 18.19.64 merge-stream: 2.0.0 supports-color: 7.2.0 jest-worker@27.5.1: dependencies: - '@types/node': 16.18.119 + '@types/node': 18.19.64 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@28.1.3: dependencies: - '@types/node': 16.18.119 + '@types/node': 18.19.64 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -18101,6 +18531,8 @@ snapshots: '@sideway/formula': 3.0.1 '@sideway/pinpoint': 2.0.0 + jose@5.9.6: {} + joycon@3.1.1: {} js-beautify@1.15.1: @@ -18318,6 +18750,8 @@ snapshots: klona@2.0.6: {} + kysely@0.27.4: {} + language-subtag-registry@0.3.23: {} language-tags@1.0.9: @@ -18589,6 +19023,11 @@ snapshots: media-typer@0.3.0: {} + memfs-browser@3.5.10302: + dependencies: + memfs: 3.5.3 + optional: true + memfs@3.5.3: dependencies: fs-monkey: 1.0.6 @@ -19102,6 +19541,10 @@ snapshots: nanoid@3.3.7: {} + nanoid@5.0.8: {} + + nanostores@0.11.3: {} + natural-compare-lite@1.4.0: {} natural-compare@1.4.0: {} @@ -19389,6 +19832,11 @@ snapshots: orderedmap@2.1.1: {} + oslo@1.2.1: + dependencies: + '@node-rs/argon2': 1.7.0 + '@node-rs/bcrypt': 1.9.0 + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -19486,18 +19934,6 @@ snapshots: no-case: 3.0.4 tslib: 2.8.1 - passport-local@1.0.0: - dependencies: - passport-strategy: 1.0.0 - - passport-strategy@1.0.0: {} - - passport@0.6.0: - dependencies: - passport-strategy: 1.0.0 - pause: 0.0.1 - utils-merge: 1.0.1 - path-case@3.0.4: dependencies: dot-case: 3.0.4 @@ -19534,8 +19970,6 @@ snapshots: dependencies: through: 2.3.8 - pause@0.0.1: {} - performance-now@2.1.0: {} picocolors@0.2.1: {} @@ -20338,6 +20772,12 @@ snapshots: punycode@2.3.1: {} + pvtsutils@1.3.6: + dependencies: + tslib: 2.8.1 + + pvutils@1.1.3: {} + q@1.5.1: {} qs@6.13.0: @@ -21022,6 +21462,8 @@ snapshots: rope-sequence@1.3.4: {} + rou3@0.5.1: {} + rst-selector-parser@2.2.3: dependencies: lodash.flattendeep: 4.4.0 @@ -21199,6 +21641,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-cookie-parser@2.7.1: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4