API: signup/login (profile creation + JWT auth) (#4)

* Add signup/login (profile creation + JWT auth)

API:
- POST /auth/signup — creates a house + user_profile (transactional),
  hashes the password with argon2, sets a JWT in an httpOnly cookie
- POST /auth/login — verifies credentials (generic 401 for both wrong
  email and wrong password, doesn't leak which), sets the cookie
- POST /auth/logout — clears the cookie
- GET /auth/me — current profile, behind requireAuth middleware
- requireAuth verifies the JWT and re-checks tokenVersion against the
  DB, so a stateless JWT can still be invalidated (password change /
  logout-everywhere, not built yet but the field is in place)

Schema: user_profiles gets password_hash + token_version (not in the
original spec doc — required for auth). New migration, with
COMMENT ON for the new columns per the established pattern.

Decisions from the auth planning discussion: JWT in httpOnly cookie
(not server-side sessions), first profile created also creates its
house, argon2 for hashing.

argon2 pinned to 0.31.2 (not ^, deliberately): 0.45.1 segfaults at
runtime on this Windows machine — reproduced consistently across bash
(sandboxed and unsandboxed) and PowerShell, while 0.31.2 works fine
with the same API. Documented in the README as a trap for future
upgrades, since `tsc`/`prisma generate` succeeding doesn't catch a
runtime native-binding crash.

Tests: Mocha (unit-style, apps/api/test/auth.test.ts) and a Cucumber
feature (apps/api/features/auth.feature) covering the full signup →
authenticated flow, duplicate email, wrong password. Both share
test-support/reset-db.ts (TRUNCATE ... CASCADE) to start each
test/scenario from a clean slate. Test-only argon2 cost parameters
(NODE_ENV=test) keep the suite fast — argon2's real cost is
deliberately expensive, which made hashing dozens of times per run
slow and occasionally timeout-flaky at default cost.

CI: added a Postgres service container to lint-and-test (previously
none — tests didn't touch a real DB), runs `prisma migrate deploy`
before the test steps.

Verified end-to-end manually against the dev server (curl): signup,
duplicate email (409), wrong password (401), valid login (200),
validation errors (400), /me with and without cookie, logout (204) —
all behave as intended. Full suite (lint, mocha, cucumber, build) run
multiple times locally with no flakiness after the timeout/cost fixes.

* Fix CI: generate Prisma Client via postinstall

CI failed with "@prisma/client did not initialize yet" — pnpm install
never ran `prisma generate`, and `prisma migrate deploy` (unlike
`migrate dev`) doesn't do it either. Worked locally only because prior
`prisma migrate dev` runs had already generated the client as a side
effect.

Adding a postinstall script fixes it for CI and for anyone cloning the
repo fresh and running plain `pnpm install`.
This commit is contained in:
kyuno053 2026-08-16 13:45:23 +02:00 committed by GitHub
parent b90817b8e1
commit 42d094764f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 1027 additions and 14 deletions

View file

@ -6,9 +6,28 @@ on:
pull_request:
branches: [main]
env:
DATABASE_URL: "postgresql://ci:ci@localhost:5432/batchcooking_ci?schema=public"
# Test-only secret, never used outside CI — real deployments must set their own.
JWT_SECRET: "ci-only-secret-not-used-anywhere-else-32chars+"
jobs:
lint-and-test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: ci
POSTGRES_PASSWORD: ci
POSTGRES_DB: batchcooking_ci
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@v4
@ -21,6 +40,7 @@ jobs:
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm --filter api exec prisma migrate deploy
- run: pnpm --filter api test
- run: pnpm --filter api test:bdd
- run: pnpm build

View file

@ -29,6 +29,8 @@ Puis **édite ces deux `.env`** pour renseigner de vrais `POSTGRES_USER`/`POSTGR
ne contiennent volontairement aucun identifiant réel (juste `changeme`), et
`docker-compose.yml` refuse de démarrer tant que `POSTGRES_USER`/`PASSWORD`/`DB` ne
sont pas définis dans `.env` — pas de valeur par défaut en dur dans les fichiers commités.
Même règle pour `apps/api/.env` : `JWT_SECRET` est **requis, sans défaut** (génère le
tien, voir le commentaire dans `apps/api/.env.example`).
### Cypress : téléchargement du binaire
@ -112,3 +114,33 @@ qui n'existent pas encore).
> `package.json`. `cucumber.cjs` évite le problème *pour sa propre config* en étant
> explicitement CommonJS ; les steps/world restent en `.ts` ESM classique et sont
> chargés via `tsx` (`NODE_OPTIONS=--import=tsx`, voir le script `test:bdd`).
## Auth (apps/api)
Inscription (création de profil + foyer) et connexion, JWT dans un cookie httpOnly.
- `POST /auth/signup``{ firstName, lastName, email, password }` → crée le foyer
(`house`) et le profil (`user_profiles`) en une transaction, pose le cookie de
session, renvoie le profil (201)
- `POST /auth/login``{ email, password }` → pose le cookie de session, renvoie le
profil (200) ; message d'erreur volontairement générique (401) que ce soit
l'email ou le mot de passe qui soit incorrect
- `POST /auth/logout` — efface le cookie (204)
- `GET /auth/me` — profil courant, nécessite le cookie de session (401 sinon)
Mots de passe hachés avec argon2. Le hash est indépendant du foyer : un profil crée
toujours son propre foyer à l'inscription (rejoindre un foyer existant n'est pas
encore implémenté).
> **argon2 : version pinnée à `0.31.2`, pas de `^`.** La version `0.45.1` (dernière au
> moment de l'écriture) segfault au runtime sur au moins une configuration Windows —
> reproduit de façon stable (bash sandboxé, bash non-sandboxé, PowerShell), alors que
> `0.31.2` fonctionne parfaitement avec la même API. Si tu montes la version, revérifie
> concrètement (`argon2.hash(...)` dans un `node -e`) avant de merger, un `pnpm build`
> qui passe ne suffit pas à détecter un crash runtime.
Les tests (Mocha + Cucumber) tournent avec un coût argon2 réduit
(`NODE_ENV=test`, voir `auth.service.ts`) — le coût par défaut est volontairement
élevé (sécurité), ce qui rendrait la suite de tests lente/instable sinon. La CI
provisionne un vrai Postgres de service (`.github/workflows/ci.yml`) et exécute
`prisma migrate deploy` avant les tests.

View file

@ -3,3 +3,12 @@ PORT=3000
# Match whatever you set in the root .env (POSTGRES_USER/PASSWORD/DB) —
# do not commit the real value.
DATABASE_URL="postgresql://changeme:changeme@localhost:5432/batchcooking?schema=public"
# Required, no default on purpose — generate your own, e.g.:
# node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"
JWT_SECRET=changeme-generate-a-real-random-secret-at-least-32-chars
# Optional — defaults shown (see src/config/env.ts)
# JWT_EXPIRES_IN=7d
# AUTH_COOKIE_NAME=session
# CORS_ORIGIN=http://localhost:5173

View file

@ -1,5 +1,6 @@
{
"extension": ["ts"],
"spec": "test/**/*.test.ts",
"node-option": ["import=tsx"]
"node-option": ["import=tsx"],
"timeout": 10000
}

View file

@ -0,0 +1,33 @@
Feature: Account creation and login
As a new user
I want to create a profile and log in
So that I can access my household's batch-cooking planning
Scenario: A visitor creates a new profile
When I sign up with the following details:
| firstName | Alice |
| lastName | Martin |
| email | alice@example.com |
| password | correct-horse-battery-staple |
Then the response status should be 201
And I am authenticated as "alice@example.com"
Scenario: A visitor cannot sign up twice with the same email
Given a profile already exists with email "alice@example.com"
When I sign up with the following details:
| firstName | Alice |
| lastName | Martin |
| email | alice@example.com |
| password | correct-horse-battery-staple |
Then the response status should be 409
Scenario: A registered user logs in with correct credentials
Given a profile already exists with email "alice@example.com" and password "correct-horse-battery-staple"
When I log in with email "alice@example.com" and password "correct-horse-battery-staple"
Then the response status should be 200
And I am authenticated as "alice@example.com"
Scenario: A registered user cannot log in with the wrong password
Given a profile already exists with email "alice@example.com" and password "correct-horse-battery-staple"
When I log in with email "alice@example.com" and password "wrong-password"
Then the response status should be 401

View file

@ -0,0 +1,44 @@
import assert from "node:assert/strict";
import type { DataTable } from "@cucumber/cucumber";
import { Given, Then, When } from "@cucumber/cucumber";
import { signup } from "../../src/modules/auth/auth.service.js";
import type { CustomWorld } from "../support/world.js";
Given("a profile already exists with email {string}", async (email: string) => {
await signup({
firstName: "Existing",
lastName: "User",
email,
password: "some-existing-password",
});
});
Given(
"a profile already exists with email {string} and password {string}",
async (email: string, password: string) => {
await signup({ firstName: "Existing", lastName: "User", email, password });
},
);
When("I sign up with the following details:", async function (this: CustomWorld, table: DataTable) {
const details = table.rowsHash();
this.response = await this.agent.post("/auth/signup").send({
firstName: details.firstName,
lastName: details.lastName,
email: details.email,
password: details.password,
});
});
When(
"I log in with email {string} and password {string}",
async function (this: CustomWorld, email: string, password: string) {
this.response = await this.agent.post("/auth/login").send({ email, password });
},
);
Then("I am authenticated as {string}", async function (this: CustomWorld, email: string) {
const res = await this.agent.get("/auth/me");
assert.equal(res.status, 200);
assert.equal(res.body.email, email);
});

View file

@ -0,0 +1,10 @@
import { Before, setDefaultTimeout } from "@cucumber/cucumber";
import { resetDatabase } from "../../test-support/reset-db.js";
// Integration tests hitting a real Postgres + argon2 need more than
// Cucumber's 5s default, especially on a cold Prisma connection.
setDefaultTimeout(10_000);
Before(async () => {
await resetDatabase();
});

View file

@ -1,17 +1,21 @@
import { type IWorldOptions, World, setWorldConstructor } from "@cucumber/cucumber";
import type { Express } from "express";
import type request from "supertest";
import request from "supertest";
import { createApp } from "../../src/app.js";
// Fresh Express app per scenario (in-process, via supertest — no server to
// spin up/tear down) plus the last HTTP response, available to every step.
// `agent` persists cookies across requests within a scenario (needed for
// "sign up then check I'm authenticated" style flows).
export class CustomWorld extends World {
app: Express;
agent: ReturnType<typeof request.agent>;
response!: request.Response;
constructor(options: IWorldOptions) {
super(options);
this.app = createApp();
this.agent = request.agent(this.app);
}
}

View file

@ -7,20 +7,28 @@
"dev": "tsx watch src/server.ts",
"build": "tsc -p tsconfig.json",
"start": "node dist/server.js",
"test": "mocha",
"test:bdd": "cross-env NODE_OPTIONS=--import=tsx cucumber-js",
"test": "cross-env NODE_ENV=test mocha",
"test:bdd": "cross-env NODE_ENV=test NODE_OPTIONS=--import=tsx cucumber-js",
"prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev"
"prisma:migrate": "prisma migrate dev",
"postinstall": "prisma generate"
},
"dependencies": {
"@prisma/client": "^5.22.0",
"argon2": "0.31.2",
"cookie-parser": "^1.4.7",
"cors": "^2.8.6",
"dotenv": "^16.4.5",
"express": "^4.21.1",
"jsonwebtoken": "^9.0.3",
"zod": "^3.23.8"
},
"devDependencies": {
"@cucumber/cucumber": "^13.2.1",
"@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.19",
"@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^22.9.0",
"@types/supertest": "^6.0.2",
"chai": "^5.1.2",

View file

@ -0,0 +1,12 @@
/*
Warnings:
- Added the required column `password_hash` to the `user_profiles` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "user_profiles" ADD COLUMN "password_hash" TEXT NOT NULL,
ADD COLUMN "token_version" INTEGER NOT NULL DEFAULT 0;
COMMENT ON COLUMN "user_profiles"."password_hash" IS 'argon2 hash of the account password (not in the original spec doc — added for authentication)';
COMMENT ON COLUMN "user_profiles"."token_version" IS 'Bumped to invalidate previously-issued JWTs, e.g. on password change (not in the original spec doc — required for stateless JWT auth)';

View file

@ -56,6 +56,12 @@ model UserProfile {
firstName String @map("first_name")
lastName String @map("last_name")
email String @unique
/// argon2 hash of the account password. Not in the original spec doc —
/// added for authentication (login page / profile creation).
passwordHash String @map("password_hash")
/// Bumped to invalidate previously-issued JWTs (e.g. on password change).
/// Not in the original spec doc — required for stateless JWT auth.
tokenVersion Int @default(0) @map("token_version")
houseId Int? @map("house_id")
dietId Int? @map("diet_id")

View file

@ -1,22 +1,39 @@
import cookieParser from "cookie-parser";
import cors from "cors";
import express, { type NextFunction, type Request, type Response } from "express";
import { ZodError } from "zod";
import { env } from "./config/env.js";
import { HttpError } from "./lib/http-error.js";
import { authRouter } from "./modules/auth/auth.routes.js";
// Application factory — no business modules yet, only generic infrastructure
// (health check, JSON parsing, 404 handler, error handler). Feature modules
// will be added once the data model / specs are defined.
// Application factory. Feature modules are added under src/modules/* as
// specs land; auth is the first one (login page / profile creation).
export function createApp() {
const app = express();
app.use(cors({ origin: env.CORS_ORIGIN, credentials: true }));
app.use(express.json());
app.use(cookieParser());
app.get("/health", (_req: Request, res: Response) => {
res.status(200).json({ status: "ok" });
});
app.use("/auth", authRouter);
app.use((_req: Request, res: Response) => {
res.status(404).json({ error: "Not found" });
});
app.use((err: unknown, _req: Request, res: Response, _next: NextFunction) => {
if (err instanceof ZodError) {
res.status(400).json({ error: "Validation error", details: err.flatten() });
return;
}
if (err instanceof HttpError) {
res.status(err.status).json({ error: err.message });
return;
}
console.error(err);
res.status(500).json({ error: "Internal server error" });
});

View file

@ -5,6 +5,13 @@ const envSchema = z.object({
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
PORT: z.coerce.number().int().positive().default(3000),
DATABASE_URL: z.string().url().optional(),
// Auth — no default on purpose, same reasoning as docker-compose.yml's
// POSTGRES_USER/PASSWORD: a secret must never have a working fallback
// baked into committed code.
JWT_SECRET: z.string().min(32, "JWT_SECRET must be at least 32 characters"),
JWT_EXPIRES_IN: z.string().default("7d"),
AUTH_COOKIE_NAME: z.string().default("session"),
CORS_ORIGIN: z.string().default("http://localhost:5173"),
});
export const env = envSchema.parse(process.env);

View file

@ -0,0 +1,5 @@
import { PrismaClient } from "@prisma/client";
// Single shared instance — Prisma manages its own connection pool
// internally, a new PrismaClient per request would exhaust connections.
export const prisma = new PrismaClient();

View file

@ -0,0 +1,11 @@
/** Typed error carrying the HTTP status it should map to, so the central
* error handler in app.ts can respond correctly instead of always 500ing. */
export class HttpError extends Error {
status: number;
constructor(status: number, message: string) {
super(message);
this.name = "HttpError";
this.status = status;
}
}

30
apps/api/src/lib/jwt.ts Normal file
View file

@ -0,0 +1,30 @@
import jwt from "jsonwebtoken";
import { env } from "../config/env.js";
export interface AuthTokenPayload {
userProfileId: number;
tokenVersion: number;
}
export function signAuthToken(payload: AuthTokenPayload): string {
// "sub" follows the JWT convention (RFC 7519) of identifying the
// principal as a string; userProfileId/tokenVersion are our own claims.
return jwt.sign(
{ sub: String(payload.userProfileId), tokenVersion: payload.tokenVersion },
env.JWT_SECRET,
{ expiresIn: env.JWT_EXPIRES_IN as jwt.SignOptions["expiresIn"] },
);
}
export function verifyAuthToken(token: string): AuthTokenPayload {
const decoded = jwt.verify(token, env.JWT_SECRET);
const userProfileId = typeof decoded === "object" ? Number(decoded.sub) : Number.NaN;
if (
typeof decoded !== "object" ||
Number.isNaN(userProfileId) ||
typeof decoded.tokenVersion !== "number"
) {
throw new Error("Malformed auth token payload");
}
return { userProfileId, tokenVersion: decoded.tokenVersion };
}

View file

@ -0,0 +1,35 @@
import type { NextFunction, Request, Response } from "express";
import { env } from "../config/env.js";
import { prisma } from "../db/prisma.js";
import { HttpError } from "../lib/http-error.js";
import { verifyAuthToken } from "../lib/jwt.js";
/** Reads the session cookie, verifies the JWT, and re-checks tokenVersion
* against the database (so a password change / logout-everywhere can
* invalidate previously-issued tokens despite JWT being stateless). */
export async function requireAuth(req: Request, _res: Response, next: NextFunction) {
try {
const token = req.cookies?.[env.AUTH_COOKIE_NAME];
if (typeof token !== "string") {
throw new HttpError(401, "Not authenticated");
}
const payload = verifyAuthToken(token);
const profile = await prisma.userProfile.findUnique({ where: { id: payload.userProfileId } });
if (!profile || profile.tokenVersion !== payload.tokenVersion) {
throw new HttpError(401, "Not authenticated");
}
const { passwordHash: _passwordHash, ...safeProfile } = profile;
req.userProfile = safeProfile;
next();
} catch (err) {
if (err instanceof HttpError) {
next(err);
} else {
// Covers jwt.verify failures (expired/invalid/malformed token).
next(new HttpError(401, "Not authenticated"));
}
}
}

View file

@ -0,0 +1,51 @@
import { Router } from "express";
import type { CookieOptions } from "express";
import { env } from "../../config/env.js";
import { requireAuth } from "../../middlewares/require-auth.js";
import { loginSchema, signupSchema } from "./auth.schema.js";
import { login, signup } from "./auth.service.js";
export const authRouter = Router();
// Independent from JWT_EXPIRES_IN on purpose (see auth.routes.ts) — the JWT's
// own expiry is what's actually enforced by requireAuth, this only bounds
// how long the browser keeps sending the cookie.
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
const cookieOptions: CookieOptions = {
httpOnly: true,
secure: env.NODE_ENV === "production",
sameSite: "lax",
maxAge: SEVEN_DAYS_MS,
};
authRouter.post("/signup", async (req, res, next) => {
try {
const input = signupSchema.parse(req.body);
const { profile, token } = await signup(input);
res.cookie(env.AUTH_COOKIE_NAME, token, cookieOptions);
res.status(201).json(profile);
} catch (err) {
next(err);
}
});
authRouter.post("/login", async (req, res, next) => {
try {
const input = loginSchema.parse(req.body);
const { profile, token } = await login(input);
res.cookie(env.AUTH_COOKIE_NAME, token, cookieOptions);
res.status(200).json(profile);
} catch (err) {
next(err);
}
});
authRouter.post("/logout", (_req, res) => {
res.clearCookie(env.AUTH_COOKIE_NAME, cookieOptions);
res.status(204).end();
});
authRouter.get("/me", requireAuth, (req, res) => {
res.status(200).json(req.userProfile);
});

View file

@ -0,0 +1,18 @@
import { z } from "zod";
export const signupSchema = z.object({
firstName: z.string().trim().min(1).max(100),
lastName: z.string().trim().min(1).max(100),
email: z.string().trim().toLowerCase().email(),
// Length only — not the place to enforce complexity rules; argon2 already
// makes brute-forcing short-but-random passwords impractical, and
// complexity rules mostly push users toward predictable patterns.
password: z.string().min(8).max(200),
});
export type SignupInput = z.infer<typeof signupSchema>;
export const loginSchema = z.object({
email: z.string().trim().toLowerCase().email(),
password: z.string().min(1),
});
export type LoginInput = z.infer<typeof loginSchema>;

View file

@ -0,0 +1,65 @@
import type { UserProfile } from "@prisma/client";
import argon2 from "argon2";
import { env } from "../../config/env.js";
import { prisma } from "../../db/prisma.js";
import { HttpError } from "../../lib/http-error.js";
import { signAuthToken } from "../../lib/jwt.js";
import type { LoginInput, SignupInput } from "./auth.schema.js";
type SafeProfile = Omit<UserProfile, "passwordHash">;
// argon2's defaults (64 MB memory, 3 passes) are deliberately expensive —
// that's the point, for real passwords. In tests we hash/verify dozens of
// times per run against throwaway data, so a much cheaper cost keeps the
// suite fast without weakening anything real. Never applies outside
// NODE_ENV=test.
const testHashOptions = { memoryCost: 8192, timeCost: 2, parallelism: 1 };
const hashOptions = env.NODE_ENV === "test" ? testHashOptions : undefined;
function toSafeProfile(profile: UserProfile): SafeProfile {
const { passwordHash: _passwordHash, ...safeProfile } = profile;
return safeProfile;
}
export async function signup(input: SignupInput): Promise<{ profile: SafeProfile; token: string }> {
const existing = await prisma.userProfile.findUnique({ where: { email: input.email } });
if (existing) {
throw new HttpError(409, "Email already in use");
}
const passwordHash = await argon2.hash(input.password, hashOptions);
// A profile always belongs to a house; signup creates one (named after
// the new user for now — renaming/joining an existing house is a
// separate, not-yet-built feature).
const profile = await prisma.$transaction(async (tx) => {
const house = await tx.house.create({
data: { name: `Foyer de ${input.firstName}` },
});
return tx.userProfile.create({
data: {
firstName: input.firstName,
lastName: input.lastName,
email: input.email,
passwordHash,
houseId: house.id,
},
});
});
const token = signAuthToken({ userProfileId: profile.id, tokenVersion: profile.tokenVersion });
return { profile: toSafeProfile(profile), token };
}
export async function login(input: LoginInput): Promise<{ profile: SafeProfile; token: string }> {
const profile = await prisma.userProfile.findUnique({ where: { email: input.email } });
// Deliberately generic error/message for both "no such email" and "wrong
// password" — don't leak which one it was.
if (!profile || !(await argon2.verify(profile.passwordHash, input.password))) {
throw new HttpError(401, "Invalid email or password");
}
const token = signAuthToken({ userProfileId: profile.id, tokenVersion: profile.tokenVersion });
return { profile: toSafeProfile(profile), token };
}

10
apps/api/src/types/express.d.ts vendored Normal file
View file

@ -0,0 +1,10 @@
import type { UserProfile } from "@prisma/client";
declare global {
namespace Express {
interface Request {
/** Set by requireAuth once the session cookie's JWT has been verified. */
userProfile?: Omit<UserProfile, "passwordHash">;
}
}
}

View file

@ -0,0 +1,15 @@
import { prisma } from "../src/db/prisma.js";
// Single TRUNCATE ... CASCADE covers FK ordering and resets identity
// sequences — used between tests/scenarios to start from a clean slate.
export async function resetDatabase() {
await prisma.$executeRawUnsafe(`
TRUNCATE TABLE
"user_profile_allergy", "allergy", "category",
"planning_item", "planning",
"recipe_ingredient", "step", "tech_step_mapping", "tech_step",
"recipe", "ingredients", "sources",
"user_profiles", "diet", "house"
RESTART IDENTITY CASCADE;
`);
}

103
apps/api/test/auth.test.ts Normal file
View file

@ -0,0 +1,103 @@
import { expect } from "chai";
import request from "supertest";
import { createApp } from "../src/app.js";
import { prisma } from "../src/db/prisma.js";
import { resetDatabase } from "../test-support/reset-db.js";
const validSignup = {
firstName: "Nicolas",
lastName: "Lefevre",
email: "nicolas@example.com",
password: "correct-horse-battery-staple",
};
describe("Auth", () => {
const app = createApp();
beforeEach(async () => {
await resetDatabase();
});
after(async () => {
await prisma.$disconnect();
});
describe("POST /auth/signup", () => {
it("creates a profile and its house, and sets a session cookie", async () => {
const res = await request(app).post("/auth/signup").send(validSignup);
expect(res.status).to.equal(201);
expect(res.body).to.include({
firstName: "Nicolas",
lastName: "Lefevre",
email: "nicolas@example.com",
});
expect(res.body).to.not.have.property("passwordHash");
expect(res.body.houseId).to.be.a("number");
expect(res.headers["set-cookie"]?.[0]).to.include("session=");
});
it("rejects a duplicate email with 409", async () => {
await request(app).post("/auth/signup").send(validSignup);
const res = await request(app).post("/auth/signup").send(validSignup);
expect(res.status).to.equal(409);
});
it("rejects an invalid payload with 400", async () => {
const res = await request(app)
.post("/auth/signup")
.send({ firstName: "X", lastName: "Y", email: "not-an-email", password: "short" });
expect(res.status).to.equal(400);
});
});
describe("POST /auth/login", () => {
beforeEach(async () => {
await request(app).post("/auth/signup").send(validSignup);
});
it("logs in with correct credentials", async () => {
const res = await request(app)
.post("/auth/login")
.send({ email: validSignup.email, password: validSignup.password });
expect(res.status).to.equal(200);
expect(res.body.email).to.equal(validSignup.email);
});
it("rejects a wrong password with 401", async () => {
const res = await request(app)
.post("/auth/login")
.send({ email: validSignup.email, password: "wrong-password" });
expect(res.status).to.equal(401);
});
it("rejects an unknown email with 401", async () => {
const res = await request(app)
.post("/auth/login")
.send({ email: "nobody@example.com", password: validSignup.password });
expect(res.status).to.equal(401);
});
});
describe("GET /auth/me", () => {
it("rejects requests without a session cookie", async () => {
const res = await request(app).get("/auth/me");
expect(res.status).to.equal(401);
});
it("returns the current profile when authenticated", async () => {
const agent = request.agent(app);
await agent.post("/auth/signup").send(validSignup);
const res = await agent.get("/auth/me");
expect(res.status).to.equal(200);
expect(res.body.email).to.equal(validSignup.email);
});
});
});

View file

@ -24,6 +24,7 @@
"@biomejs/biome",
"@prisma/client",
"@prisma/engines",
"argon2",
"cypress",
"esbuild",
"prisma"

View file

@ -20,12 +20,24 @@ importers:
'@prisma/client':
specifier: ^5.22.0
version: 5.22.0(prisma@5.22.0)
argon2:
specifier: 0.31.2
version: 0.31.2
cookie-parser:
specifier: ^1.4.7
version: 1.4.7
cors:
specifier: ^2.8.6
version: 2.8.6
dotenv:
specifier: ^16.4.5
version: 16.6.1
express:
specifier: ^4.21.1
version: 4.22.2
jsonwebtoken:
specifier: ^9.0.3
version: 9.0.3
zod:
specifier: ^3.23.8
version: 3.25.76
@ -33,9 +45,18 @@ importers:
'@cucumber/cucumber':
specifier: ^13.2.1
version: 13.2.1
'@types/cookie-parser':
specifier: ^1.4.10
version: 1.4.10(@types/express@4.17.25)
'@types/cors':
specifier: ^2.8.19
version: 2.8.19
'@types/express':
specifier: ^4.17.21
version: 4.17.25
'@types/jsonwebtoken':
specifier: ^9.0.10
version: 9.0.10
'@types/node':
specifier: ^22.9.0
version: 22.20.1
@ -654,6 +675,10 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==, tarball: https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz}
'@mapbox/node-pre-gyp@1.0.11':
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==, tarball: https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz}
hasBin: true
'@napi-rs/lzma-linux-x64-gnu@1.5.1':
resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==, tarball: https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz}
engines: {node: ^22.20 || ^24.12 || >=25}
@ -667,6 +692,10 @@ packages:
'@paralleldrive/cuid2@2.3.1':
resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==, tarball: https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz}
'@phc/format@1.0.0':
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==, tarball: https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz}
engines: {node: '>=10'}
'@prisma/client@5.22.0':
resolution: {integrity: sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==, tarball: https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz}
engines: {node: '>=16.13'}
@ -844,9 +873,17 @@ packages:
'@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==, tarball: https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz}
'@types/cookie-parser@1.4.10':
resolution: {integrity: sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==, tarball: https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz}
peerDependencies:
'@types/express': '*'
'@types/cookiejar@2.1.5':
resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==, tarball: https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz}
'@types/cors@2.8.19':
resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==, tarball: https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz}
'@types/estree@1.0.9':
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==, tarball: https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz}
@ -859,12 +896,18 @@ packages:
'@types/http-errors@2.0.5':
resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==, tarball: https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz}
'@types/jsonwebtoken@9.0.10':
resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==, tarball: https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz}
'@types/methods@1.1.4':
resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==, tarball: https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz}
'@types/mime@1.3.5':
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==, tarball: https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz}
'@types/ms@2.1.0':
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==, tarball: https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz}
'@types/node@22.20.1':
resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==, tarball: https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz}
@ -918,6 +961,9 @@ packages:
peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==, tarball: https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz}
accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==, tarball: https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz}
engines: {node: '>= 0.6'}
@ -954,12 +1000,24 @@ packages:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==, tarball: https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz}
engines: {node: '>= 8'}
aproba@2.1.0:
resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==, tarball: https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz}
arch@2.2.0:
resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==, tarball: https://registry.npmjs.org/arch/-/arch-2.2.0.tgz}
are-we-there-yet@2.0.0:
resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==, tarball: https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz}
engines: {node: '>=10'}
deprecated: This package is no longer supported.
arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==, tarball: https://registry.npmjs.org/arg/-/arg-5.0.2.tgz}
argon2@0.31.2:
resolution: {integrity: sha512-QSnJ8By5Mth60IEte45w9Y7v6bWcQw3YhRtJKKN8oNCxnTLDiv/AXXkDPf2srTMfxFVn3QJdVv2nhXESsUa+Yg==, tarball: https://registry.npmjs.org/argon2/-/argon2-0.31.2.tgz}
engines: {node: '>=14.0.0'}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, tarball: https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz}
@ -1034,6 +1092,9 @@ packages:
resolution: {integrity: sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==, tarball: https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
brace-expansion@1.1.18:
resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==, tarball: https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz}
brace-expansion@2.1.4:
resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==, tarball: https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz}
@ -1052,6 +1113,9 @@ packages:
buffer-crc32@0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==, tarball: https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz}
buffer-equal-constant-time@1.0.1:
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==, tarball: https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz}
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==, tarball: https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz}
@ -1104,6 +1168,10 @@ packages:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==, tarball: https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz}
engines: {node: '>= 8.10.0'}
chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==, tarball: https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz}
engines: {node: '>=10'}
ci-info@4.4.0:
resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==, tarball: https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz}
engines: {node: '>=8'}
@ -1134,6 +1202,10 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, tarball: https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz}
color-support@1.1.3:
resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==, tarball: https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz}
hasBin: true
colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==, tarball: https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz}
@ -1156,6 +1228,12 @@ packages:
component-emitter@1.3.1:
resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==, tarball: https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz}
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, tarball: https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz}
console-control-strings@1.1.0:
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==, tarball: https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz}
content-disposition@0.5.4:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==, tarball: https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz}
engines: {node: '>= 0.6'}
@ -1167,6 +1245,13 @@ packages:
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, tarball: https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz}
cookie-parser@1.4.7:
resolution: {integrity: sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==, tarball: https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz}
engines: {node: '>= 0.8.0'}
cookie-signature@1.0.6:
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==, tarball: https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz}
cookie-signature@1.0.7:
resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==, tarball: https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz}
@ -1184,6 +1269,10 @@ packages:
core-util-is@1.0.2:
resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==, tarball: https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz}
cors@2.8.6:
resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==, tarball: https://registry.npmjs.org/cors/-/cors-2.8.6.tgz}
engines: {node: '>= 0.10'}
cross-env@10.1.0:
resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==, tarball: https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz}
engines: {node: '>=20'}
@ -1245,6 +1334,9 @@ packages:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, tarball: https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz}
engines: {node: '>=0.4.0'}
delegates@1.0.0:
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==, tarball: https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz}
depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==, tarball: https://registry.npmjs.org/depd/-/depd-2.0.0.tgz}
engines: {node: '>= 0.8'}
@ -1253,6 +1345,10 @@ packages:
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==, tarball: https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==, tarball: https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz}
engines: {node: '>=8'}
dezalgo@1.0.4:
resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==, tarball: https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz}
@ -1278,6 +1374,9 @@ packages:
ecc-jsbn@0.1.2:
resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==, tarball: https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz}
ecdsa-sig-formatter@1.0.11:
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==, tarball: https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz}
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==, tarball: https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz}
@ -1449,6 +1548,10 @@ packages:
resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==, tarball: https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz}
engines: {node: '>=10'}
fs-minipass@2.1.0:
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==, tarball: https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz}
engines: {node: '>= 8'}
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==, tarball: https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz}
@ -1460,6 +1563,11 @@ packages:
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, tarball: https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz}
gauge@3.0.2:
resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==, tarball: https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz}
engines: {node: '>=10'}
deprecated: This package is no longer supported.
gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, tarball: https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz}
engines: {node: '>=6.9.0'}
@ -1494,6 +1602,10 @@ packages:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, tarball: https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz}
engines: {node: '>= 6'}
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==, tarball: https://registry.npmjs.org/glob/-/glob-7.2.3.tgz}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
glob@8.1.0:
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==, tarball: https://registry.npmjs.org/glob/-/glob-8.1.0.tgz}
engines: {node: '>=12'}
@ -1530,6 +1642,9 @@ packages:
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==, tarball: https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz}
engines: {node: '>= 0.4'}
has-unicode@2.0.1:
resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==, tarball: https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz}
hasown@2.0.4:
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==, tarball: https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz}
engines: {node: '>= 0.4'}
@ -1694,10 +1809,20 @@ packages:
jsonfile@6.2.1:
resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==, tarball: https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz}
jsonwebtoken@9.0.3:
resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==, tarball: https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz}
engines: {node: '>=12', npm: '>=6'}
jsprim@2.0.2:
resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==, tarball: https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz}
engines: {'0': node >=0.6.0}
jwa@2.0.1:
resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==, tarball: https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz}
jws@4.0.1:
resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==, tarball: https://registry.npmjs.org/jws/-/jws-4.0.1.tgz}
knuth-shuffle-seeded@1.0.6:
resolution: {integrity: sha512-9pFH0SplrfyKyojCLxZfMcvkhf5hH0d+UwR9nTVJ/DDQJGuzcXjTwB7TP7sDfehSudlGGaOLblmEWqv04ERVWg==, tarball: https://registry.npmjs.org/knuth-shuffle-seeded/-/knuth-shuffle-seeded-1.0.6.tgz}
@ -1718,6 +1843,24 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, tarball: https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz}
engines: {node: '>=10'}
lodash.includes@4.3.0:
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==, tarball: https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz}
lodash.isboolean@3.0.3:
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==, tarball: https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz}
lodash.isinteger@4.0.4:
resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==, tarball: https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz}
lodash.isnumber@3.0.3:
resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==, tarball: https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz}
lodash.isplainobject@4.0.6:
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==, tarball: https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz}
lodash.isstring@4.0.1:
resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==, tarball: https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz}
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, tarball: https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz}
@ -1759,6 +1902,10 @@ packages:
resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==, tarball: https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz}
engines: {node: '>=12'}
make-dir@3.1.0:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==, tarball: https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz}
engines: {node: '>=8'}
map-stream@0.1.0:
resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==, tarball: https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz}
@ -1807,6 +1954,9 @@ packages:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==, tarball: https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz}
engines: {node: '>=6'}
minimatch@3.1.5:
resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==, tarball: https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz}
minimatch@5.1.9:
resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==, tarball: https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz}
engines: {node: '>=10'}
@ -1814,6 +1964,23 @@ packages:
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==, tarball: https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz}
minipass@3.3.6:
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==, tarball: https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz}
engines: {node: '>=8'}
minipass@5.0.0:
resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==, tarball: https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz}
engines: {node: '>=8'}
minizlib@2.1.2:
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==, tarball: https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz}
engines: {node: '>= 8'}
mkdirp@1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==, tarball: https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz}
engines: {node: '>=10'}
hasBin: true
mocha@10.8.2:
resolution: {integrity: sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==, tarball: https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz}
engines: {node: '>= 14.0.0'}
@ -1834,10 +2001,27 @@ packages:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==, tarball: https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz}
engines: {node: '>= 0.6'}
node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==, tarball: https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz}
node-fetch@2.7.0:
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==, tarball: https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz}
engines: {node: 4.x || >=6.0.0}
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
node-releases@2.0.53:
resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==, tarball: https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz}
engines: {node: '>=18'}
nopt@5.0.0:
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==, tarball: https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz}
engines: {node: '>=6'}
hasBin: true
normalize-package-data@8.0.0:
resolution: {integrity: sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==, tarball: https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz}
engines: {node: ^20.17.0 || >=22.9.0}
@ -1850,6 +2034,14 @@ packages:
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==, tarball: https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz}
engines: {node: '>=8'}
npmlog@5.0.1:
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==, tarball: https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz}
deprecated: This package is no longer supported.
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==, tarball: https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz}
engines: {node: '>=0.10.0'}
object-inspect@1.13.4:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==, tarball: https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz}
engines: {node: '>= 0.4'}
@ -1896,6 +2088,10 @@ packages:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, tarball: https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz}
engines: {node: '>=8'}
path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==, tarball: https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz}
engines: {node: '>=0.10.0'}
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, tarball: https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz}
engines: {node: '>=8'}
@ -2006,6 +2202,10 @@ packages:
resolution: {integrity: sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==, tarball: https://registry.npmjs.org/read-pkg/-/read-pkg-10.1.0.tgz}
engines: {node: '>=20'}
readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==, tarball: https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz}
engines: {node: '>= 6'}
readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==, tarball: https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz}
engines: {node: '>=8.10.0'}
@ -2035,6 +2235,11 @@ packages:
rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==, tarball: https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz}
rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==, tarball: https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz}
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
rollup@4.62.4:
resolution: {integrity: sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==, tarball: https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@ -2075,6 +2280,9 @@ packages:
resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==, tarball: https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz}
engines: {node: '>= 0.8.0'}
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==, tarball: https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz}
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==, tarball: https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz}
@ -2167,6 +2375,9 @@ packages:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, tarball: https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz}
engines: {node: '>=8'}
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==, tarball: https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz}
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, tarball: https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz}
engines: {node: '>=8'}
@ -2203,6 +2414,11 @@ packages:
resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==, tarball: https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz}
engines: {node: '>=20'}
tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==, tarball: https://registry.npmjs.org/tar/-/tar-6.2.1.tgz}
engines: {node: '>=10'}
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
throttleit@1.0.1:
resolution: {integrity: sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==, tarball: https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz}
@ -2238,6 +2454,9 @@ packages:
resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==, tarball: https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz}
engines: {node: '>=16'}
tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==, tarball: https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz}
tree-kill@1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==, tarball: https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz}
hasBin: true
@ -2309,6 +2528,9 @@ packages:
util-arity@1.1.0:
resolution: {integrity: sha512-kkyIsXKwemfSy8ZEoaIz06ApApnWsk5hQO0vLjZS6UkBiGiW++Jsyb8vSBoc0WKlffGoGs5yYy/j5pp8zckrFA==, tarball: https://registry.npmjs.org/util-arity/-/util-arity-1.1.0.tgz}
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, tarball: https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz}
utils-merge@1.0.1:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==, tarball: https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz}
engines: {node: '>= 0.4.0'}
@ -2365,11 +2587,20 @@ packages:
engines: {node: '>=20.0.0'}
hasBin: true
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==, tarball: https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz}
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==, tarball: https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz}
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, tarball: https://registry.npmjs.org/which/-/which-2.0.2.tgz}
engines: {node: '>= 8'}
hasBin: true
wide-align@1.1.5:
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==, tarball: https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz}
workerpool@6.5.1:
resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==, tarball: https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz}
@ -2395,6 +2626,9 @@ packages:
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, tarball: https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz}
yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==, tarball: https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz}
yaml@2.9.0:
resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==, tarball: https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz}
engines: {node: '>= 14.6'}
@ -2895,6 +3129,21 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@mapbox/node-pre-gyp@1.0.11':
dependencies:
detect-libc: 2.1.2
https-proxy-agent: 5.0.1
make-dir: 3.1.0
node-fetch: 2.7.0
nopt: 5.0.0
npmlog: 5.0.1
rimraf: 3.0.2
semver: 7.8.5
tar: 6.2.1
transitivePeerDependencies:
- encoding
- supports-color
'@napi-rs/lzma-linux-x64-gnu@1.5.1':
optional: true
@ -2904,6 +3153,8 @@ snapshots:
dependencies:
'@noble/hashes': 1.8.0
'@phc/format@1.0.0': {}
'@prisma/client@5.22.0(prisma@5.22.0)':
optionalDependencies:
prisma: 5.22.0
@ -3040,8 +3291,16 @@ snapshots:
dependencies:
'@types/node': 22.20.1
'@types/cookie-parser@1.4.10(@types/express@4.17.25)':
dependencies:
'@types/express': 4.17.25
'@types/cookiejar@2.1.5': {}
'@types/cors@2.8.19':
dependencies:
'@types/node': 22.20.1
'@types/estree@1.0.9': {}
'@types/express-serve-static-core@4.19.9':
@ -3060,10 +3319,17 @@ snapshots:
'@types/http-errors@2.0.5': {}
'@types/jsonwebtoken@9.0.10':
dependencies:
'@types/ms': 2.1.0
'@types/node': 22.20.1
'@types/methods@1.1.4': {}
'@types/mime@1.3.5': {}
'@types/ms@2.1.0': {}
'@types/node@22.20.1':
dependencies:
undici-types: 6.21.0
@ -3133,6 +3399,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
abbrev@1.1.1: {}
accepts@1.3.8:
dependencies:
mime-types: 2.1.35
@ -3168,10 +3436,26 @@ snapshots:
normalize-path: 3.0.0
picomatch: 2.3.2
aproba@2.1.0: {}
arch@2.2.0: {}
are-we-there-yet@2.0.0:
dependencies:
delegates: 1.0.0
readable-stream: 3.6.2
arg@5.0.2: {}
argon2@0.31.2:
dependencies:
'@mapbox/node-pre-gyp': 1.0.11
'@phc/format': 1.0.0
node-addon-api: 7.1.1
transitivePeerDependencies:
- encoding
- supports-color
argparse@2.0.1: {}
array-flatten@1.1.1: {}
@ -3247,6 +3531,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
brace-expansion@1.1.18:
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
brace-expansion@2.1.4:
dependencies:
balanced-match: 1.0.2
@ -3267,6 +3556,8 @@ snapshots:
buffer-crc32@0.2.13: {}
buffer-equal-constant-time@1.0.1: {}
buffer-from@1.1.2: {}
buffer@5.7.1:
@ -3323,6 +3614,8 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
chownr@2.0.0: {}
ci-info@4.4.0: {}
clean-stack@2.2.0: {}
@ -3354,6 +3647,8 @@ snapshots:
color-name@1.1.4: {}
color-support@1.1.3: {}
colorette@2.0.20: {}
combined-stream@1.0.8:
@ -3368,6 +3663,10 @@ snapshots:
component-emitter@1.3.1: {}
concat-map@0.0.1: {}
console-control-strings@1.1.0: {}
content-disposition@0.5.4:
dependencies:
safe-buffer: 5.2.1
@ -3376,6 +3675,13 @@ snapshots:
convert-source-map@2.0.0: {}
cookie-parser@1.4.7:
dependencies:
cookie: 0.7.2
cookie-signature: 1.0.6
cookie-signature@1.0.6: {}
cookie-signature@1.0.7: {}
cookie-signature@1.2.2: {}
@ -3386,6 +3692,11 @@ snapshots:
core-util-is@1.0.2: {}
cors@2.8.6:
dependencies:
object-assign: 4.1.1
vary: 1.1.2
cross-env@10.1.0:
dependencies:
'@epic-web/invariant': 1.0.0
@ -3483,10 +3794,14 @@ snapshots:
delayed-stream@1.0.0: {}
delegates@1.0.0: {}
depd@2.0.0: {}
destroy@1.2.0: {}
detect-libc@2.1.2: {}
dezalgo@1.0.4:
dependencies:
asap: 2.0.6
@ -3511,6 +3826,10 @@ snapshots:
jsbn: 0.1.1
safer-buffer: 2.1.2
ecdsa-sig-formatter@1.0.11:
dependencies:
safe-buffer: 5.2.1
ee-first@1.1.1: {}
electron-to-chromium@1.5.407: {}
@ -3774,6 +4093,10 @@ snapshots:
jsonfile: 6.2.1
universalify: 2.0.1
fs-minipass@2.1.0:
dependencies:
minipass: 3.3.6
fs.realpath@1.0.0: {}
fsevents@2.3.3:
@ -3781,6 +4104,18 @@ snapshots:
function-bind@1.1.2: {}
gauge@3.0.2:
dependencies:
aproba: 2.1.0
color-support: 1.1.3
console-control-strings: 1.1.0
has-unicode: 2.0.1
object-assign: 4.1.1
signal-exit: 3.0.7
string-width: 4.2.3
strip-ansi: 6.0.1
wide-align: 1.1.5
gensync@1.0.0-beta.2: {}
get-caller-file@2.0.5: {}
@ -3821,6 +4156,15 @@ snapshots:
dependencies:
is-glob: 4.0.3
glob@7.2.3:
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.1.5
once: 1.4.0
path-is-absolute: 1.0.1
glob@8.1.0:
dependencies:
fs.realpath: 1.0.0
@ -3853,6 +4197,8 @@ snapshots:
dependencies:
has-symbols: 1.1.0
has-unicode@2.0.1: {}
hasown@2.0.4:
dependencies:
function-bind: 1.1.2
@ -3987,6 +4333,19 @@ snapshots:
optionalDependencies:
graceful-fs: 4.2.11
jsonwebtoken@9.0.3:
dependencies:
jws: 4.0.1
lodash.includes: 4.3.0
lodash.isboolean: 3.0.3
lodash.isinteger: 4.0.4
lodash.isnumber: 3.0.3
lodash.isplainobject: 4.0.6
lodash.isstring: 4.0.1
lodash.once: 4.1.1
ms: 2.1.3
semver: 7.8.5
jsprim@2.0.2:
dependencies:
assert-plus: 1.0.0
@ -3994,6 +4353,17 @@ snapshots:
json-schema: 0.4.0
verror: 1.10.0
jwa@2.0.1:
dependencies:
buffer-equal-constant-time: 1.0.1
ecdsa-sig-formatter: 1.0.11
safe-buffer: 5.2.1
jws@4.0.1:
dependencies:
jwa: 2.0.1
safe-buffer: 5.2.1
knuth-shuffle-seeded@1.0.6:
dependencies:
seed-random: 2.2.0
@ -4017,6 +4387,18 @@ snapshots:
dependencies:
p-locate: 5.0.0
lodash.includes@4.3.0: {}
lodash.isboolean@3.0.3: {}
lodash.isinteger@4.0.4: {}
lodash.isnumber@3.0.3: {}
lodash.isplainobject@4.0.6: {}
lodash.isstring@4.0.1: {}
lodash.merge@4.6.2: {}
lodash.mergewith@4.6.2: {}
@ -4053,6 +4435,10 @@ snapshots:
luxon@3.7.2: {}
make-dir@3.1.0:
dependencies:
semver: 6.3.1
map-stream@0.1.0: {}
math-intrinsics@1.1.0: {}
@ -4079,12 +4465,29 @@ snapshots:
mimic-fn@2.1.0: {}
minimatch@3.1.5:
dependencies:
brace-expansion: 1.1.18
minimatch@5.1.9:
dependencies:
brace-expansion: 2.1.4
minimist@1.2.8: {}
minipass@3.3.6:
dependencies:
yallist: 4.0.0
minipass@5.0.0: {}
minizlib@2.1.2:
dependencies:
minipass: 3.3.6
yallist: 4.0.0
mkdirp@1.0.4: {}
mocha@10.8.2:
dependencies:
ansi-colors: 4.1.3
@ -4116,8 +4519,18 @@ snapshots:
negotiator@0.6.3: {}
node-addon-api@7.1.1: {}
node-fetch@2.7.0:
dependencies:
whatwg-url: 5.0.0
node-releases@2.0.53: {}
nopt@5.0.0:
dependencies:
abbrev: 1.1.1
normalize-package-data@8.0.0:
dependencies:
hosted-git-info: 9.0.3
@ -4130,6 +4543,15 @@ snapshots:
dependencies:
path-key: 3.1.1
npmlog@5.0.1:
dependencies:
are-we-there-yet: 2.0.0
console-control-strings: 1.1.0
gauge: 3.0.2
set-blocking: 2.0.0
object-assign@4.1.1: {}
object-inspect@1.13.4: {}
on-finished@2.4.1:
@ -4172,6 +4594,8 @@ snapshots:
path-exists@4.0.0: {}
path-is-absolute@1.0.1: {}
path-key@3.1.1: {}
path-to-regexp@0.1.13: {}
@ -4276,6 +4700,12 @@ snapshots:
type-fest: 5.8.0
unicorn-magic: 0.4.0
readable-stream@3.6.2:
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
readdirp@3.6.0:
dependencies:
picomatch: 2.3.2
@ -4301,6 +4731,10 @@ snapshots:
rfdc@1.4.1: {}
rimraf@3.0.2:
dependencies:
glob: 7.2.3
rollup@4.62.4:
dependencies:
'@types/estree': 1.0.9
@ -4382,6 +4816,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
set-blocking@2.0.0: {}
setprototypeof@1.2.0: {}
shebang-command@2.0.0:
@ -4500,6 +4936,10 @@ snapshots:
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
@ -4542,6 +4982,15 @@ snapshots:
tagged-tag@1.0.0: {}
tar@6.2.1:
dependencies:
chownr: 2.0.0
fs-minipass: 2.1.0
minipass: 5.0.0
minizlib: 2.1.2
mkdirp: 1.0.4
yallist: 4.0.0
throttleit@1.0.1: {}
through@2.3.8: {}
@ -4568,6 +5017,8 @@ snapshots:
dependencies:
tldts: 6.1.86
tr46@0.0.3: {}
tree-kill@1.2.2: {}
tslib@2.8.1: {}
@ -4619,6 +5070,8 @@ snapshots:
util-arity@1.1.0: {}
util-deprecate@1.0.2: {}
utils-merge@1.0.1: {}
uuid@8.3.2: {}
@ -4656,10 +5109,21 @@ snapshots:
- debug
- supports-color
webidl-conversions@3.0.1: {}
whatwg-url@5.0.0:
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
which@2.0.2:
dependencies:
isexe: 2.0.0
wide-align@1.1.5:
dependencies:
string-width: 4.2.3
workerpool@6.5.1: {}
wrap-ansi@6.2.0:
@ -4682,6 +5146,8 @@ snapshots:
yallist@3.1.1: {}
yallist@4.0.0: {}
yaml@2.9.0: {}
yargs-parser@20.2.9: {}