feat(api): add auth middleware to protect game endpoints
- Add requireAuth middleware using Better Auth session validation - Apply to all game routes (start, answer) - Unauthenticated requests return 401
This commit is contained in:
parent
91a3112d8b
commit
a3685a9e68
13 changed files with 196 additions and 24 deletions
|
|
@ -13,9 +13,11 @@
|
||||||
"@glossa/db": "workspace:*",
|
"@glossa/db": "workspace:*",
|
||||||
"@glossa/shared": "workspace:*",
|
"@glossa/shared": "workspace:*",
|
||||||
"better-auth": "^1.6.2",
|
"better-auth": "^1.6.2",
|
||||||
|
"cors": "^2.8.6",
|
||||||
"express": "^5.2.1"
|
"express": "^5.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/cors": "^2.8.19",
|
||||||
"@types/express": "^5.0.6",
|
"@types/express": "^5.0.6",
|
||||||
"@types/supertest": "^7.2.0",
|
"@types/supertest": "^7.2.0",
|
||||||
"supertest": "^7.2.2",
|
"supertest": "^7.2.2",
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,12 @@ import { toNodeHandler } from "better-auth/node";
|
||||||
import { auth } from "./lib/auth.js";
|
import { auth } from "./lib/auth.js";
|
||||||
import { apiRouter } from "./routes/apiRouter.js";
|
import { apiRouter } from "./routes/apiRouter.js";
|
||||||
import { errorHandler } from "./middleware/errorHandler.js";
|
import { errorHandler } from "./middleware/errorHandler.js";
|
||||||
|
import cors from "cors";
|
||||||
|
|
||||||
export function createApp() {
|
export function createApp() {
|
||||||
const app: Express = express();
|
const app: Express = express();
|
||||||
|
|
||||||
|
app.use(cors({ origin: "http://localhost:5173", credentials: true }));
|
||||||
app.all("/api/auth/*splat", toNodeHandler(auth));
|
app.all("/api/auth/*splat", toNodeHandler(auth));
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use("/api/v1", apiRouter);
|
app.use("/api/v1", apiRouter);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from "better-auth";
|
||||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||||
import { db } from "@glossa/db";
|
import { db } from "@glossa/db";
|
||||||
|
import * as schema from "@glossa/db/schema";
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
database: drizzleAdapter(db, { provider: "pg" }),
|
database: drizzleAdapter(db, { provider: "pg", schema }),
|
||||||
|
trustedOrigins: ["http://localhost:5173"],
|
||||||
socialProviders: {
|
socialProviders: {
|
||||||
google: {
|
google: {
|
||||||
clientId: process.env["GOOGLE_CLIENT_ID"] as string,
|
clientId: process.env["GOOGLE_CLIENT_ID"] as string,
|
||||||
|
|
|
||||||
20
apps/api/src/middleware/authMiddleware.ts
Normal file
20
apps/api/src/middleware/authMiddleware.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import type { Request, Response, NextFunction } from "express";
|
||||||
|
import { fromNodeHeaders } from "better-auth/node";
|
||||||
|
import { auth } from "../lib/auth.js";
|
||||||
|
|
||||||
|
export const requireAuth = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction,
|
||||||
|
) => {
|
||||||
|
const session = await auth.api.getSession({
|
||||||
|
headers: fromNodeHeaders(req.headers),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
res.status(401).json({ success: false, error: "Unauthorized" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import type { Router } from "express";
|
import type { Router } from "express";
|
||||||
import { createGame, submitAnswer } from "../controllers/gameController.js";
|
import { createGame, submitAnswer } from "../controllers/gameController.js";
|
||||||
|
import { requireAuth } from "../middleware/authMiddleware.js";
|
||||||
|
|
||||||
export const gameRouter: Router = express.Router();
|
export const gameRouter: Router = express.Router();
|
||||||
|
|
||||||
|
gameRouter.use(requireAuth);
|
||||||
gameRouter.post("/start", createGame);
|
gameRouter.post("/start", createGame);
|
||||||
gameRouter.post("/answer", submitAnswer);
|
gameRouter.post("/answer", submitAnswer);
|
||||||
|
|
|
||||||
7
apps/web/src/lib/auth-client.ts
Normal file
7
apps/web/src/lib/auth-client.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { createAuthClient } from "better-auth/react";
|
||||||
|
|
||||||
|
export const authClient = createAuthClient({
|
||||||
|
baseURL: "http://localhost:3000",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { signIn, signOut, useSession } = authClient;
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
import { Route as rootRouteImport } from './routes/__root'
|
import { Route as rootRouteImport } from './routes/__root'
|
||||||
import { Route as PlayRouteImport } from './routes/play'
|
import { Route as PlayRouteImport } from './routes/play'
|
||||||
|
import { Route as LoginRouteImport } from './routes/login'
|
||||||
import { Route as AboutRouteImport } from './routes/about'
|
import { Route as AboutRouteImport } from './routes/about'
|
||||||
import { Route as IndexRouteImport } from './routes/index'
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
|
|
||||||
|
|
@ -18,6 +19,11 @@ const PlayRoute = PlayRouteImport.update({
|
||||||
path: '/play',
|
path: '/play',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
|
const LoginRoute = LoginRouteImport.update({
|
||||||
|
id: '/login',
|
||||||
|
path: '/login',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
const AboutRoute = AboutRouteImport.update({
|
const AboutRoute = AboutRouteImport.update({
|
||||||
id: '/about',
|
id: '/about',
|
||||||
path: '/about',
|
path: '/about',
|
||||||
|
|
@ -32,30 +38,34 @@ const IndexRoute = IndexRouteImport.update({
|
||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/about': typeof AboutRoute
|
'/about': typeof AboutRoute
|
||||||
|
'/login': typeof LoginRoute
|
||||||
'/play': typeof PlayRoute
|
'/play': typeof PlayRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/about': typeof AboutRoute
|
'/about': typeof AboutRoute
|
||||||
|
'/login': typeof LoginRoute
|
||||||
'/play': typeof PlayRoute
|
'/play': typeof PlayRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
__root__: typeof rootRouteImport
|
__root__: typeof rootRouteImport
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/about': typeof AboutRoute
|
'/about': typeof AboutRoute
|
||||||
|
'/login': typeof LoginRoute
|
||||||
'/play': typeof PlayRoute
|
'/play': typeof PlayRoute
|
||||||
}
|
}
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
fullPaths: '/' | '/about' | '/play'
|
fullPaths: '/' | '/about' | '/login' | '/play'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to: '/' | '/about' | '/play'
|
to: '/' | '/about' | '/login' | '/play'
|
||||||
id: '__root__' | '/' | '/about' | '/play'
|
id: '__root__' | '/' | '/about' | '/login' | '/play'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute
|
IndexRoute: typeof IndexRoute
|
||||||
AboutRoute: typeof AboutRoute
|
AboutRoute: typeof AboutRoute
|
||||||
|
LoginRoute: typeof LoginRoute
|
||||||
PlayRoute: typeof PlayRoute
|
PlayRoute: typeof PlayRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,6 +78,13 @@ declare module '@tanstack/react-router' {
|
||||||
preLoaderRoute: typeof PlayRouteImport
|
preLoaderRoute: typeof PlayRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
|
'/login': {
|
||||||
|
id: '/login'
|
||||||
|
path: '/login'
|
||||||
|
fullPath: '/login'
|
||||||
|
preLoaderRoute: typeof LoginRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
'/about': {
|
'/about': {
|
||||||
id: '/about'
|
id: '/about'
|
||||||
path: '/about'
|
path: '/about'
|
||||||
|
|
@ -88,6 +105,7 @@ declare module '@tanstack/react-router' {
|
||||||
const rootRouteChildren: RootRouteChildren = {
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
AboutRoute: AboutRoute,
|
AboutRoute: AboutRoute,
|
||||||
|
LoginRoute: LoginRoute,
|
||||||
PlayRoute: PlayRoute,
|
PlayRoute: PlayRoute,
|
||||||
}
|
}
|
||||||
export const routeTree = rootRouteImport
|
export const routeTree = rootRouteImport
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,51 @@
|
||||||
import { createRootRoute, Link, Outlet } from "@tanstack/react-router";
|
import {
|
||||||
|
createRootRoute,
|
||||||
|
Link,
|
||||||
|
Outlet,
|
||||||
|
useNavigate,
|
||||||
|
} from "@tanstack/react-router";
|
||||||
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
||||||
|
import { useSession, signOut } from "../lib/auth-client";
|
||||||
|
|
||||||
const RootLayout = () => (
|
const RootLayout = () => {
|
||||||
<>
|
const { data: session } = useSession();
|
||||||
<div className="p-2 flex gap-2">
|
const navigate = useNavigate();
|
||||||
<Link to="/" className="[&.active]:font-bold">
|
|
||||||
Home
|
return (
|
||||||
</Link>{" "}
|
<>
|
||||||
<Link to="/about" className="[&.active]:font-bold">
|
<div className="p-2 flex gap-2 items-center">
|
||||||
About
|
<Link to="/" className="[&.active]:font-bold">
|
||||||
</Link>
|
Home
|
||||||
</div>
|
</Link>
|
||||||
<hr />
|
<Link to="/about" className="[&.active]:font-bold">
|
||||||
<Outlet />
|
About
|
||||||
<TanStackRouterDevtools />
|
</Link>
|
||||||
</>
|
<div className="ml-auto">
|
||||||
);
|
{session ? (
|
||||||
|
<button
|
||||||
|
className="text-sm text-gray-600 hover:text-gray-900"
|
||||||
|
onClick={async () => {
|
||||||
|
await signOut();
|
||||||
|
navigate({ to: "/" });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Sign out ({session.user.name})
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
to="/login"
|
||||||
|
className="text-sm text-blue-600 hover:text-blue-800"
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<Outlet />
|
||||||
|
<TanStackRouterDevtools />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const Route = createRootRoute({ component: RootLayout });
|
export const Route = createRootRoute({ component: RootLayout });
|
||||||
|
|
|
||||||
44
apps/web/src/routes/login.tsx
Normal file
44
apps/web/src/routes/login.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
||||||
|
import { signIn, useSession } from "../lib/auth-client";
|
||||||
|
|
||||||
|
const LoginPage = () => {
|
||||||
|
const { data: session, isPending } = useSession();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
if (isPending) return <div className="p-4">Loading...</div>;
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
navigate({ to: "/" });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center gap-4 p-8">
|
||||||
|
<h1 className="text-2xl font-bold">Sign in to Glossa</h1>
|
||||||
|
<button
|
||||||
|
className="w-64 rounded bg-gray-800 px-4 py-2 text-white hover:bg-gray-700"
|
||||||
|
onClick={() =>
|
||||||
|
signIn.social({
|
||||||
|
provider: "github",
|
||||||
|
callbackURL: "http://localhost:5173",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Continue with GitHub
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="w-64 rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-500"
|
||||||
|
onClick={() =>
|
||||||
|
signIn.social({
|
||||||
|
provider: "google",
|
||||||
|
callbackURL: "http://localhost:5173",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Continue with Google
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/login")({ component: LoginPage });
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
import { useState, useCallback } from "react";
|
import { useState, useCallback } from "react";
|
||||||
import type { GameSession, GameRequest, AnswerResult } from "@glossa/shared";
|
import type { GameSession, GameRequest, AnswerResult } from "@glossa/shared";
|
||||||
import { QuestionCard } from "../components/game/QuestionCard";
|
import { QuestionCard } from "../components/game/QuestionCard";
|
||||||
import { ScoreScreen } from "../components/game/ScoreScreen";
|
import { ScoreScreen } from "../components/game/ScoreScreen";
|
||||||
import { GameSetup } from "../components/game/GameSetup";
|
import { GameSetup } from "../components/game/GameSetup";
|
||||||
|
import { authClient } from "../lib/auth-client";
|
||||||
|
|
||||||
function Play() {
|
function Play() {
|
||||||
const [gameSession, setGameSession] = useState<GameSession | null>(null);
|
const [gameSession, setGameSession] = useState<GameSession | null>(null);
|
||||||
|
|
@ -105,4 +106,12 @@ function Play() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Route = createFileRoute("/play")({ component: Play });
|
export const Route = createFileRoute("/play")({
|
||||||
|
component: Play,
|
||||||
|
beforeLoad: async () => {
|
||||||
|
const { data: session } = await authClient.getSession();
|
||||||
|
if (!session) {
|
||||||
|
throw redirect({ to: "/login" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
|
"composite": false,
|
||||||
|
"declaration": false,
|
||||||
|
"declarationMap": false,
|
||||||
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
|
|
@ -9,7 +12,7 @@
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"target": "ES2023",
|
"target": "ES2023",
|
||||||
"types": ["vite/client", "vitest/globals"],
|
"types": ["vite/client", "vitest/globals"],
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo"
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
},
|
},
|
||||||
"include": ["src", "vitest.config.ts"]
|
"include": ["src", "vitest.config.ts"],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@
|
||||||
|
|
||||||
## problems+thoughts
|
## problems+thoughts
|
||||||
|
|
||||||
|
### try now option
|
||||||
|
|
||||||
|
there should be an option to try the app without an account so users can see what they would get when creating an account
|
||||||
|
|
||||||
### resolve deps problem
|
### resolve deps problem
|
||||||
|
|
||||||
pnpm --filter web add better-auth
|
pnpm --filter web add better-auth
|
||||||
|
|
|
||||||
28
pnpm-lock.yaml
generated
28
pnpm-lock.yaml
generated
|
|
@ -56,10 +56,16 @@ importers:
|
||||||
better-auth:
|
better-auth:
|
||||||
specifier: ^1.6.2
|
specifier: ^1.6.2
|
||||||
version: 1.6.2(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.28.16)(pg@8.20.0))(pg@8.20.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)))
|
version: 1.6.2(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.28.16)(pg@8.20.0))(pg@8.20.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)))
|
||||||
|
cors:
|
||||||
|
specifier: ^2.8.6
|
||||||
|
version: 2.8.6
|
||||||
express:
|
express:
|
||||||
specifier: ^5.2.1
|
specifier: ^5.2.1
|
||||||
version: 5.2.1
|
version: 5.2.1
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@types/cors':
|
||||||
|
specifier: ^2.8.19
|
||||||
|
version: 2.8.19
|
||||||
'@types/express':
|
'@types/express':
|
||||||
specifier: ^5.0.6
|
specifier: ^5.0.6
|
||||||
version: 5.0.6
|
version: 5.0.6
|
||||||
|
|
@ -1243,6 +1249,9 @@ packages:
|
||||||
'@types/cookiejar@2.1.5':
|
'@types/cookiejar@2.1.5':
|
||||||
resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==}
|
resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==}
|
||||||
|
|
||||||
|
'@types/cors@2.8.19':
|
||||||
|
resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==}
|
||||||
|
|
||||||
'@types/deep-eql@4.0.2':
|
'@types/deep-eql@4.0.2':
|
||||||
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
|
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
|
||||||
|
|
||||||
|
|
@ -1662,6 +1671,10 @@ packages:
|
||||||
cookiejar@2.1.4:
|
cookiejar@2.1.4:
|
||||||
resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==}
|
resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==}
|
||||||
|
|
||||||
|
cors@2.8.6:
|
||||||
|
resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
|
||||||
|
engines: {node: '>= 0.10'}
|
||||||
|
|
||||||
crc-32@1.2.2:
|
crc-32@1.2.2:
|
||||||
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
|
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
|
||||||
engines: {node: '>=0.8'}
|
engines: {node: '>=0.8'}
|
||||||
|
|
@ -2397,6 +2410,10 @@ packages:
|
||||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
object-assign@4.1.1:
|
||||||
|
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
object-inspect@1.13.4:
|
object-inspect@1.13.4:
|
||||||
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
|
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
@ -3821,6 +3838,10 @@ snapshots:
|
||||||
|
|
||||||
'@types/cookiejar@2.1.5': {}
|
'@types/cookiejar@2.1.5': {}
|
||||||
|
|
||||||
|
'@types/cors@2.8.19':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 24.12.0
|
||||||
|
|
||||||
'@types/deep-eql@4.0.2': {}
|
'@types/deep-eql@4.0.2': {}
|
||||||
|
|
||||||
'@types/esrecurse@4.3.1': {}
|
'@types/esrecurse@4.3.1': {}
|
||||||
|
|
@ -4306,6 +4327,11 @@ snapshots:
|
||||||
|
|
||||||
cookiejar@2.1.4: {}
|
cookiejar@2.1.4: {}
|
||||||
|
|
||||||
|
cors@2.8.6:
|
||||||
|
dependencies:
|
||||||
|
object-assign: 4.1.1
|
||||||
|
vary: 1.1.2
|
||||||
|
|
||||||
crc-32@1.2.2: {}
|
crc-32@1.2.2: {}
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
|
|
@ -4985,6 +5011,8 @@ snapshots:
|
||||||
|
|
||||||
normalize-path@3.0.0: {}
|
normalize-path@3.0.0: {}
|
||||||
|
|
||||||
|
object-assign@4.1.1: {}
|
||||||
|
|
||||||
object-inspect@1.13.4: {}
|
object-inspect@1.13.4: {}
|
||||||
|
|
||||||
obug@2.1.1: {}
|
obug@2.1.1: {}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue