Compare commits

..

12 Commits

50 changed files with 4396 additions and 214 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.env

474
backup.sql Normal file
View File

@ -0,0 +1,474 @@
--
-- PostgreSQL database dump
--
-- Dumped from database version 15.12 (Debian 15.12-1.pgdg120+1)
-- Dumped by pg_dump version 15.12 (Debian 15.12-1.pgdg120+1)
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;
SET default_tablespace = '';
SET default_table_access_method = heap;
--
-- Name: gallery_images; Type: TABLE; Schema: public; Owner: tgadmin
--
CREATE TABLE public.gallery_images (
id integer NOT NULL,
image_url text NOT NULL,
title text,
uploaded_at timestamp without time zone DEFAULT now()
);
ALTER TABLE public.gallery_images OWNER TO tgadmin;
--
-- Name: gallery_images_id_seq; Type: SEQUENCE; Schema: public; Owner: tgadmin
--
CREATE SEQUENCE public.gallery_images_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER TABLE public.gallery_images_id_seq OWNER TO tgadmin;
--
-- Name: gallery_images_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: tgadmin
--
ALTER SEQUENCE public.gallery_images_id_seq OWNED BY public.gallery_images.id;
--
-- Name: news; Type: TABLE; Schema: public; Owner: tgadmin
--
CREATE TABLE public.news (
id integer NOT NULL,
title character varying(255) NOT NULL,
description text NOT NULL,
image_url text,
team character varying(255),
created_at timestamp without time zone DEFAULT now()
);
ALTER TABLE public.news OWNER TO tgadmin;
--
-- Name: news_id_seq; Type: SEQUENCE; Schema: public; Owner: tgadmin
--
CREATE SEQUENCE public.news_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER TABLE public.news_id_seq OWNER TO tgadmin;
--
-- Name: news_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: tgadmin
--
ALTER SEQUENCE public.news_id_seq OWNED BY public.news.id;
--
-- Name: player_teams; Type: TABLE; Schema: public; Owner: tgadmin
--
CREATE TABLE public.player_teams (
player_id integer NOT NULL,
team_id integer NOT NULL
);
ALTER TABLE public.player_teams OWNER TO tgadmin;
--
-- Name: players; Type: TABLE; Schema: public; Owner: tgadmin
--
CREATE TABLE public.players (
id integer NOT NULL,
name character varying(255) NOT NULL,
nickname character varying(255),
birthdate date,
"position" character varying(255) NOT NULL,
jersey_number integer,
favorite_food character varying(255),
image_url text,
status character varying(50) DEFAULT 'aktiv'::character varying
);
ALTER TABLE public.players OWNER TO tgadmin;
--
-- Name: players_id_seq; Type: SEQUENCE; Schema: public; Owner: tgadmin
--
CREATE SEQUENCE public.players_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER TABLE public.players_id_seq OWNER TO tgadmin;
--
-- Name: players_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: tgadmin
--
ALTER SEQUENCE public.players_id_seq OWNED BY public.players.id;
--
-- Name: teams; Type: TABLE; Schema: public; Owner: tgadmin
--
CREATE TABLE public.teams (
id integer NOT NULL,
name character varying(255) NOT NULL,
liga text,
sucht_spieler boolean DEFAULT false,
social_media text,
karussell_bilder text[],
trainer_name text,
trainingszeiten text,
trainingsort text,
kontakt_name text,
kontakt_email text,
teamfarben text,
beschreibung text,
tabellenlink text
);
ALTER TABLE public.teams OWNER TO tgadmin;
--
-- Name: teams_id_seq; Type: SEQUENCE; Schema: public; Owner: tgadmin
--
CREATE SEQUENCE public.teams_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER TABLE public.teams_id_seq OWNER TO tgadmin;
--
-- Name: teams_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: tgadmin
--
ALTER SEQUENCE public.teams_id_seq OWNED BY public.teams.id;
--
-- Name: users; Type: TABLE; Schema: public; Owner: tgadmin
--
CREATE TABLE public.users (
id integer NOT NULL,
username character varying(255) NOT NULL,
password character varying(255) NOT NULL,
role character varying(50),
email character varying(255),
created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP,
last_logged timestamp without time zone
);
ALTER TABLE public.users OWNER TO tgadmin;
--
-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: tgadmin
--
CREATE SEQUENCE public.users_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER TABLE public.users_id_seq OWNER TO tgadmin;
--
-- Name: users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: tgadmin
--
ALTER SEQUENCE public.users_id_seq OWNED BY public.users.id;
--
-- Name: gallery_images id; Type: DEFAULT; Schema: public; Owner: tgadmin
--
ALTER TABLE ONLY public.gallery_images ALTER COLUMN id SET DEFAULT nextval('public.gallery_images_id_seq'::regclass);
--
-- Name: news id; Type: DEFAULT; Schema: public; Owner: tgadmin
--
ALTER TABLE ONLY public.news ALTER COLUMN id SET DEFAULT nextval('public.news_id_seq'::regclass);
--
-- Name: players id; Type: DEFAULT; Schema: public; Owner: tgadmin
--
ALTER TABLE ONLY public.players ALTER COLUMN id SET DEFAULT nextval('public.players_id_seq'::regclass);
--
-- Name: teams id; Type: DEFAULT; Schema: public; Owner: tgadmin
--
ALTER TABLE ONLY public.teams ALTER COLUMN id SET DEFAULT nextval('public.teams_id_seq'::regclass);
--
-- Name: users id; Type: DEFAULT; Schema: public; Owner: tgadmin
--
ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_id_seq'::regclass);
--
-- Data for Name: gallery_images; Type: TABLE DATA; Schema: public; Owner: tgadmin
--
COPY public.gallery_images (id, image_url, title, uploaded_at) FROM stdin;
1 /uploads/gallery/1745995571128.jpg \N 2025-04-30 06:46:11.135241
2 /uploads/gallery/1745995574092.jpg \N 2025-04-30 06:46:14.14359
3 /uploads/gallery/1745995576651.png \N 2025-04-30 06:46:16.693676
5 /uploads/gallery/1745995582496.jpg \N 2025-04-30 06:46:22.543915
7 /uploads/gallery/1745995795079.jpg \N 2025-04-30 06:49:55.087993
8 /uploads/gallery/1745995797723.jpg \N 2025-04-30 06:49:57.773579
9 /uploads/gallery/1745995800923.png \N 2025-04-30 06:50:00.973585
10 /uploads/gallery/1745995804788.jpg \N 2025-04-30 06:50:04.833931
11 /uploads/gallery/1745995810186.png \N 2025-04-30 06:50:10.23422
13 /uploads/gallery/1745997000203.png \N 2025-04-30 07:10:00.204411
14 /uploads/gallery/1745997004338.png \N 2025-04-30 07:10:04.383564
\.
--
-- Data for Name: news; Type: TABLE DATA; Schema: public; Owner: tgadmin
--
COPY public.news (id, title, description, image_url, team, created_at) FROM stdin;
1 Neue News vorhanden <p>Diese hat sogar <strong>Rich Text Support </strong>enabled!</p> /uploads/news/1745994332500.jpg Herren 1 2025-04-29 07:39:47.266851
\.
--
-- Data for Name: player_teams; Type: TABLE DATA; Schema: public; Owner: tgadmin
--
COPY public.player_teams (player_id, team_id) FROM stdin;
1 1
3 1
4 1
5 1
6 1
7 1
8 1
2 1
9 4
10 4
11 4
12 4
13 4
\.
--
-- Data for Name: players; Type: TABLE DATA; Schema: public; Owner: tgadmin
--
COPY public.players (id, name, nickname, birthdate, "position", jersey_number, favorite_food, image_url, status) FROM stdin;
6 Lasse Höfer LassEs! \N Außen/Annahme 6 LasseRanja /uploads/players/1745913205538.jpg aktiv
1 Marc Wieland Marci \N Diagonal 20 Dönerfleisch /uploads/players/1745913211650.jpg aktiv
7 Peter Roller Brrm Brrm \N Zuspieler 4 Reis mit Hühnchen /uploads/players/1745913222839.jpg aktiv
5 Phillip Schaefer Hifi \N Libero 13 Dönerfleisch /uploads/players/1745913229017.jpg aktiv
3 Samuel Valentino Quintero Artigas (Be)samu \N Mitte 8 Pizza /uploads/players/1745913236094.jpg aktiv
4 Sten Grüner Stenislav \N Außen/Annahme 3 Borschtsch /uploads/players/1745913243174.jpg aktiv
8 Tony Fan Tony Mahoni \N Zuspieler 11 Asia-Wok /uploads/players/1745913254103.jpg aktiv
2 David Brockmüller Blocki \N Mitte/Diagonal 12 Keks /uploads/players/1745913167980.jpg aktiv
9 Ann-Kathrin Minden Annki \N Mitte 9 Cola /uploads/players/1745928485551.jpg aktiv
10 Luisa Trautmann Lulu/Lu \N Außen/Annahme 6 Kenneth /uploads/players/1745928513832.jpg aktiv
11 Paula Jüllich Pauli \N Mitte 10 Döner vom Özefe /uploads/players/1745928548483.jpg aktiv
12 Annika Braasch Anni \N Zuspiel 2 Sebastian /uploads/players/1745928569406.jpg aktiv
13 Sebastian Minden Basti \N Trainer \N Whisky /uploads/players/1745928620598.jpg aktiv
\.
--
-- Data for Name: teams; Type: TABLE DATA; Schema: public; Owner: tgadmin
--
COPY public.teams (id, name, liga, sucht_spieler, social_media, karussell_bilder, trainer_name, trainingszeiten, trainingsort, kontakt_name, kontakt_email, teamfarben, beschreibung, tabellenlink) FROM stdin;
2 Herren 2 \N f \N \N \N \N \N \N \N \N \N \N
5 Damen 2 \N f \N \N \N \N \N \N \N \N \N \N
4 Damen 1 Landesliga t instagram.com/tgl_volleyball {} Sebastian Minden Montags & Freitags 20-22 Uhr Bergstraßenhalle Laudenbach Sebastian Minden sebastian@minden.de Grün Wir sind die Damen 1, eine super Truppe mit viel Zug zum Erfolg! https://www.volleyball-nordbaden.de/cms/home/halle/spielbetrieb/tabellen_vl_ll.xhtml?LeaguePresenter.view=resultTable&LeaguePresenter.matchSeriesId=70120527#samsCmsComponent_68828342
1 Herren 1 Landesliga f www.google.de {} Kahtrin Trübenbach Montags & Freitags, 19:30-22 Uhr Montags: Bergstraßenhalle Laudenbach, Freitags: BIZ Hemsbach Kathrin Trübenbach trübi@bach.de Grün Wir sind die H1 und wir machen Hackfleisch aus dir! https://www.volleyball-nordbaden.de/cms/home/halle/spielbetrieb/tabellen_vl_ll.xhtml?LeaguePresenter.view=resultTable&LeaguePresenter.matchSeriesId=70120580#samsCmsComponent_68828342
9 Mixed Bezirksklasse t {} Wolf Blecher Freitags: 20-22 Uhr Bergstraßenhalle Laudenbach Wolf Blecher wolf@blech.de Grün Spiel und Spaß für jung und alt!
\.
--
-- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: tgadmin
--
COPY public.users (id, username, password, role, email, created_at, last_logged) FROM stdin;
2 kennyschrubber $2b$10$mHgcSphPt.WsNrEnYlM.i.wPVCeWnkd4bGCPpn2B50fdTOK9DvhxK user kennyschrubber@web.de 2025-04-23 13:19:15.946587 \N
4 hackerino $2b$10$DYv1lN3kivPCvdDnE4oSxOrXz0Bu.7K5EEz72RnjbgE9iemL3N/yS user hackerino@web.de 2025-04-23 13:29:57.510891 \N
1 marcwieland $2b$10$X0RgZYuVMBpIuGUM2PvGHew.siTgdaiJg0TbJ6sDZd0JPgXZII6IS admin wieland.marc@gmx.de 2025-04-23 12:00:02.440704 2025-04-30 06:45:53.31351
\.
--
-- Name: gallery_images_id_seq; Type: SEQUENCE SET; Schema: public; Owner: tgadmin
--
SELECT pg_catalog.setval('public.gallery_images_id_seq', 14, true);
--
-- Name: news_id_seq; Type: SEQUENCE SET; Schema: public; Owner: tgadmin
--
SELECT pg_catalog.setval('public.news_id_seq', 1, true);
--
-- Name: players_id_seq; Type: SEQUENCE SET; Schema: public; Owner: tgadmin
--
SELECT pg_catalog.setval('public.players_id_seq', 13, true);
--
-- Name: teams_id_seq; Type: SEQUENCE SET; Schema: public; Owner: tgadmin
--
SELECT pg_catalog.setval('public.teams_id_seq', 9, true);
--
-- Name: users_id_seq; Type: SEQUENCE SET; Schema: public; Owner: tgadmin
--
SELECT pg_catalog.setval('public.users_id_seq', 4, true);
--
-- Name: gallery_images gallery_images_pkey; Type: CONSTRAINT; Schema: public; Owner: tgadmin
--
ALTER TABLE ONLY public.gallery_images
ADD CONSTRAINT gallery_images_pkey PRIMARY KEY (id);
--
-- Name: news news_pkey; Type: CONSTRAINT; Schema: public; Owner: tgadmin
--
ALTER TABLE ONLY public.news
ADD CONSTRAINT news_pkey PRIMARY KEY (id);
--
-- Name: player_teams player_teams_pkey; Type: CONSTRAINT; Schema: public; Owner: tgadmin
--
ALTER TABLE ONLY public.player_teams
ADD CONSTRAINT player_teams_pkey PRIMARY KEY (player_id, team_id);
--
-- Name: players players_pkey; Type: CONSTRAINT; Schema: public; Owner: tgadmin
--
ALTER TABLE ONLY public.players
ADD CONSTRAINT players_pkey PRIMARY KEY (id);
--
-- Name: teams teams_name_key; Type: CONSTRAINT; Schema: public; Owner: tgadmin
--
ALTER TABLE ONLY public.teams
ADD CONSTRAINT teams_name_key UNIQUE (name);
--
-- Name: teams teams_pkey; Type: CONSTRAINT; Schema: public; Owner: tgadmin
--
ALTER TABLE ONLY public.teams
ADD CONSTRAINT teams_pkey PRIMARY KEY (id);
--
-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: tgadmin
--
ALTER TABLE ONLY public.users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
--
-- Name: player_teams player_teams_player_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: tgadmin
--
ALTER TABLE ONLY public.player_teams
ADD CONSTRAINT player_teams_player_id_fkey FOREIGN KEY (player_id) REFERENCES public.players(id) ON DELETE CASCADE;
--
-- Name: player_teams player_teams_team_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: tgadmin
--
ALTER TABLE ONLY public.player_teams
ADD CONSTRAINT player_teams_team_id_fkey FOREIGN KEY (team_id) REFERENCES public.teams(id) ON DELETE CASCADE;
--
-- PostgreSQL database dump complete
--

208
index.js
View File

@ -5,21 +5,39 @@ const multer = require('multer');
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
//Rate Limit
const rateLimit = require('express-rate-limit');
const cors = require('cors'); const cors = require('cors');
const app = express(); const app = express();
const port = process.env.PORT || 3000; const port = process.env.PORT || 3000;
const pool = new Pool({
user: process.env.DB_USER,
host: process.env.DB_HOST,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
port: process.env.DB_PORT,
});
const bcrypt = require('bcryptjs'); const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const JWT_SECRET = "supergeheimes_tg_cms_secret"; const JWT_SECRET = process.env.JWT_SECRET;
//Rate Limiter fuer Logins
const rateLimit = require("express-rate-limit");
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: "Zu viele Login-Versuche. Bitte in 15 Minuten erneut versuchen.",
standardHeaders: true,
legacyHeaders: false,
});
// Bodyparser für JSON aktivieren // Bodyparser für JSON aktivieren
app.use(cors({ app.use(cors({
origin: ["http://localhost:8080", "http://192.168.50.65:8080"], origin: ["http://volleyball.marc-wieland.de", "https://volleyball.marc-wieland.de"],
methods: ["GET", "POST", "PUT", "DELETE"], methods: ["GET", "POST", "PUT", "DELETE"],
credentials: true credentials: true
})); }));
@ -28,14 +46,7 @@ app.use(express.json());
app.use('/uploads', express.static(path.join(__dirname, 'uploads'))); app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
// PostgreSQL Verbindung aufbauen
const pool = new Pool({
user: "tgadmin",
host: "db", // Docker Container Name!
database: "tg-cms",
password: "secretpass",
port: 5432,
});
// Testroute API-Check // Testroute API-Check
app.get("/api/hello", (req, res) => { app.get("/api/hello", (req, res) => {
@ -152,18 +163,6 @@ app.post("/api/users", async (req, res) => {
} }
}); });
//Rate Limiter fuer Login
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 9999,
standardHeaders: true,
legacyHeaders: false,
});
// Benutzer Login // Benutzer Login
app.post("/api/login", loginLimiter, async (req, res) => { app.post("/api/login", loginLimiter, async (req, res) => {
const { username, password } = req.body; const { username, password } = req.body;
@ -224,7 +223,7 @@ app.get("/api/users", async (req, res) => {
} }
}); });
// Benutzer loeschen // Benutzer l<EFBFBD>loeschen
app.delete("/api/users/:id", async (req, res) => { app.delete("/api/users/:id", async (req, res) => {
const { id } = req.params; const { id } = req.params;
@ -322,6 +321,7 @@ app.post("/api/upload-news-image", uploadNewsImage.single("image"), (req, res) =
//Teams abfragen //Teams abfragen
app.get("/api/teams", async (req, res) => { app.get("/api/teams", async (req, res) => {
try { try {
const result = await pool.query(` const result = await pool.query(`
@ -329,10 +329,12 @@ app.get("/api/teams", async (req, res) => {
t.id, t.id,
t.name, t.name,
t.liga, t.liga,
t.beschreibung,
t.trainingszeiten,
t.sucht_spieler, t.sucht_spieler,
t.social_media, t.social_media,
t.trainer_name, t.trainer_name,
t.karussell_bilder, -- 👉 Hier angepasst! t.karussell_bilder,
COUNT(pt.player_id) AS player_count COUNT(pt.player_id) AS player_count
FROM teams t FROM teams t
LEFT JOIN player_teams pt ON pt.team_id = t.id LEFT JOIN player_teams pt ON pt.team_id = t.id
@ -340,10 +342,12 @@ app.get("/api/teams", async (req, res) => {
t.id, t.id,
t.name, t.name,
t.liga, t.liga,
t.beschreibung,
t.trainingszeiten,
t.sucht_spieler, t.sucht_spieler,
t.social_media, t.social_media,
t.trainer_name, t.trainer_name,
t.karussell_bilder -- 👉 Hier auch t.karussell_bilder
ORDER BY t.name ASC ORDER BY t.name ASC
`); `);
res.json(result.rows); res.json(result.rows);
@ -354,6 +358,8 @@ app.get("/api/teams", async (req, res) => {
}); });
//Team aktualisieren //Team aktualisieren
app.put("/api/teams/:id", async (req, res) => { app.put("/api/teams/:id", async (req, res) => {
const { id } = req.params; const { id } = req.params;
@ -812,6 +818,152 @@ app.post("/api/players/:id/assign-team", async (req, res) => {
//Multer Storage fuer Galleriebilder
const galleryStorage = multer.diskStorage({
destination: (req, file, cb) => {
const dir = "./uploads/gallery";
fs.mkdirSync(dir, { recursive: true });
cb(null, dir);
},
filename: (req, file, cb) => {
const ext = path.extname(file.originalname);
const filename = Date.now() + ext;
cb(null, filename);
},
});
const uploadGalleryImage = multer({ storage: galleryStorage });
//Gallery Image Upload
app.post("/api/gallery", uploadGalleryImage.single("image"), async (req, res) => {
const { title } = req.body;
if (!req.file) return res.status(400).send("Kein Bild hochgeladen");
const imageUrl = `/uploads/gallery/${req.file.filename}`;
try {
const result = await pool.query(
"INSERT INTO gallery_images (image_url, title) VALUES ($1, $2) RETURNING *",
[imageUrl, title || null]
);
res.status(201).json(result.rows[0]);
} catch (err) {
console.error("Fehler beim Speichern des Galeriebilds:", err);
res.status(500).send("Fehler beim Speichern");
}
});
//Get all gallery images
app.get("/api/gallery", async (req, res) => {
try {
const result = await pool.query("SELECT * FROM gallery_images ORDER BY uploaded_at DESC");
res.json(result.rows);
} catch (err) {
console.error("Fehler beim Laden der Galerie:", err);
res.status(500).send("Fehler beim Laden");
}
});
//Delete image
app.delete("/api/gallery/:id", async (req, res) => {
const { id } = req.params;
try {
const result = await pool.query("DELETE FROM gallery_images WHERE id = $1 RETURNING *", [id]);
if (result.rowCount === 0) return res.status(404).send("Bild nicht gefunden");
// Optional: Datei auch vom Dateisystem löschen
const filePath = path.join(__dirname, result.rows[0].image_url);
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
res.send("Bild erfolgreich gelöscht");
} catch (err) {
console.error("Fehler beim Löschen des Bildes:", err);
res.status(500).send("Fehler beim Löschen");
}
});
//Event laden
app.get("/api/events", async (req, res) => {
const { showPrivate } = req.query;
const query = showPrivate === "true"
? "SELECT * FROM events ORDER BY created_at DESC"
: "SELECT * FROM events WHERE is_private = FALSE ORDER BY created_at DESC";
const result = await pool.query(query);
res.json(result.rows);
});
//Event erstellen
app.post("/api/events", async (req, res) => {
const { title, description, max_participants, fee, address, is_private } = req.body;
const result = await pool.query(
`INSERT INTO events (title, description, max_participants, fee, address, is_private)
VALUES ($1, $2, $3, $4, $5, $6) RETURNING *`,
[title, description, max_participants, fee, address, is_private]
);
res.status(201).json(result.rows[0]);
});
//Event editieren
app.put("/api/events/:id", async (req, res) => {
const { id } = req.params;
const { title, description, max_participants, fee, address, is_private } = req.body;
const result = await pool.query(
`UPDATE events
SET title = $1,
description = $2,
max_participants = $3,
fee = $4,
address = $5,
is_private = $6
WHERE id = $7
RETURNING *`,
[title, description, max_participants, fee, address, is_private, id]
);
if (result.rowCount === 0) {
return res.status(404).json({ message: "Event not found" });
}
res.json(result.rows[0]);
});
//Event loeschen
app.delete("/api/events/:id", async (req, res) => {
const { id } = req.params;
const result = await pool.query(`DELETE FROM events WHERE id = $1`, [id]);
if (result.rowCount === 0) {
return res.status(404).json({ message: "Event not found" });
}
res.status(204).send(); // No Content
});

17
node_modules/.bin/bcrypt generated vendored
View File

@ -1,16 +1 @@
#!/bin/sh ../bcryptjs/bin/bcrypt
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../bcryptjs/bin/bcrypt" "$@"
else
exec node "$basedir/../bcryptjs/bin/bcrypt" "$@"
fi

17
node_modules/.bin/bcrypt.cmd generated vendored
View File

@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\bcryptjs\bin\bcrypt" %*

28
node_modules/.bin/bcrypt.ps1 generated vendored
View File

@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../bcryptjs/bin/bcrypt" $args
} else {
& "$basedir/node$exe" "$basedir/../bcryptjs/bin/bcrypt" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../bcryptjs/bin/bcrypt" $args
} else {
& "node$exe" "$basedir/../bcryptjs/bin/bcrypt" $args
}
$ret=$LASTEXITCODE
}
exit $ret

17
node_modules/.bin/mkdirp generated vendored
View File

@ -1,16 +1 @@
#!/bin/sh ../mkdirp/bin/cmd.js
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../mkdirp/bin/cmd.js" "$@"
else
exec node "$basedir/../mkdirp/bin/cmd.js" "$@"
fi

17
node_modules/.bin/mkdirp.cmd generated vendored
View File

@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\mkdirp\bin\cmd.js" %*

28
node_modules/.bin/mkdirp.ps1 generated vendored
View File

@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
} else {
& "$basedir/node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
} else {
& "node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

17
node_modules/.bin/semver generated vendored
View File

@ -1,16 +1 @@
#!/bin/sh ../semver/bin/semver.js
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../semver/bin/semver.js" "$@"
else
exec node "$basedir/../semver/bin/semver.js" "$@"
fi

17
node_modules/.bin/semver.cmd generated vendored
View File

@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\semver\bin\semver.js" %*

28
node_modules/.bin/semver.ps1 generated vendored
View File

@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args
} else {
& "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../semver/bin/semver.js" $args
} else {
& "node$exe" "$basedir/../semver/bin/semver.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

15
node_modules/.package-lock.json generated vendored
View File

@ -216,7 +216,6 @@
"version": "16.5.0", "version": "16.5.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
"integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
"license": "BSD-2-Clause",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -349,6 +348,20 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/express-rate-limit": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
"integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/express-rate-limit"
},
"peerDependencies": {
"express": "^4.11 || 5 || ^5.0.0-beta.1"
}
},
"node_modules/finalhandler": { "node_modules/finalhandler": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",

838
node_modules/express-rate-limit/dist/index.cjs generated vendored Normal file
View File

@ -0,0 +1,838 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// source/index.ts
var source_exports = {};
__export(source_exports, {
MemoryStore: () => MemoryStore,
default: () => lib_default,
rateLimit: () => lib_default
});
module.exports = __toCommonJS(source_exports);
// source/headers.ts
var import_node_buffer = require("buffer");
var import_node_crypto = require("crypto");
var SUPPORTED_DRAFT_VERSIONS = ["draft-6", "draft-7", "draft-8"];
var getResetSeconds = (resetTime, windowMs) => {
let resetSeconds = void 0;
if (resetTime) {
const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
resetSeconds = Math.max(0, deltaSeconds);
} else if (windowMs) {
resetSeconds = Math.ceil(windowMs / 1e3);
}
return resetSeconds;
};
var getPartitionKey = (key) => {
const hash = (0, import_node_crypto.createHash)("sha256");
hash.update(key);
const partitionKey = hash.digest("hex").slice(0, 12);
return import_node_buffer.Buffer.from(partitionKey).toString("base64");
};
var setLegacyHeaders = (response, info) => {
if (response.headersSent)
return;
response.setHeader("X-RateLimit-Limit", info.limit.toString());
response.setHeader("X-RateLimit-Remaining", info.remaining.toString());
if (info.resetTime instanceof Date) {
response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
response.setHeader(
"X-RateLimit-Reset",
Math.ceil(info.resetTime.getTime() / 1e3).toString()
);
}
};
var setDraft6Headers = (response, info, windowMs) => {
if (response.headersSent)
return;
const windowSeconds = Math.ceil(windowMs / 1e3);
const resetSeconds = getResetSeconds(info.resetTime);
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
response.setHeader("RateLimit-Limit", info.limit.toString());
response.setHeader("RateLimit-Remaining", info.remaining.toString());
if (resetSeconds)
response.setHeader("RateLimit-Reset", resetSeconds.toString());
};
var setDraft7Headers = (response, info, windowMs) => {
if (response.headersSent)
return;
const windowSeconds = Math.ceil(windowMs / 1e3);
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
response.setHeader(
"RateLimit",
`limit=${info.limit}, remaining=${info.remaining}, reset=${resetSeconds}`
);
};
var setDraft8Headers = (response, info, windowMs, name, key) => {
if (response.headersSent)
return;
const windowSeconds = Math.ceil(windowMs / 1e3);
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
const partitionKey = getPartitionKey(key);
const policy = `q=${info.limit}; w=${windowSeconds}; pk=:${partitionKey}:`;
const header = `r=${info.remaining}; t=${resetSeconds}`;
response.append("RateLimit-Policy", `"${name}"; ${policy}`);
response.append("RateLimit", `"${name}"; ${header}`);
};
var setRetryAfterHeader = (response, info, windowMs) => {
if (response.headersSent)
return;
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
response.setHeader("Retry-After", resetSeconds.toString());
};
// source/validations.ts
var import_node_net = require("net");
var ValidationError = class extends Error {
/**
* The code must be a string, in snake case and all capital, that starts with
* the substring `ERR_ERL_`.
*
* The message must be a string, starting with an uppercase character,
* describing the issue in detail.
*/
constructor(code, message) {
const url = `https://express-rate-limit.github.io/${code}/`;
super(`${message} See ${url} for more information.`);
this.name = this.constructor.name;
this.code = code;
this.help = url;
}
};
var ChangeWarning = class extends ValidationError {
};
var usedStores = /* @__PURE__ */ new Set();
var singleCountKeys = /* @__PURE__ */ new WeakMap();
var validations = {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
enabled: {
default: true
},
// Should be EnabledValidations type, but that's a circular reference
disable() {
for (const k of Object.keys(this.enabled))
this.enabled[k] = false;
},
/**
* Checks whether the IP address is valid, and that it does not have a port
* number in it.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.
*
* @param ip {string | undefined} - The IP address provided by Express as request.ip.
*
* @returns {void}
*/
ip(ip) {
if (ip === void 0) {
throw new ValidationError(
"ERR_ERL_UNDEFINED_IP_ADDRESS",
`An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.`
);
}
if (!(0, import_node_net.isIP)(ip)) {
throw new ValidationError(
"ERR_ERL_INVALID_IP_ADDRESS",
`An invalid 'request.ip' (${ip}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`
);
}
},
/**
* Makes sure the trust proxy setting is not set to `true`.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.
*
* @param request {Request} - The Express request object.
*
* @returns {void}
*/
trustProxy(request) {
if (request.app.get("trust proxy") === true) {
throw new ValidationError(
"ERR_ERL_PERMISSIVE_TRUST_PROXY",
`The Express 'trust proxy' setting is true, which allows anyone to trivially bypass IP-based rate limiting.`
);
}
},
/**
* Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
* header is present.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.
*
* @param request {Request} - The Express request object.
*
* @returns {void}
*/
xForwardedForHeader(request) {
if (request.headers["x-forwarded-for"] && request.app.get("trust proxy") === false) {
throw new ValidationError(
"ERR_ERL_UNEXPECTED_X_FORWARDED_FOR",
`The 'X-Forwarded-For' header is set but the Express 'trust proxy' setting is false (default). This could indicate a misconfiguration which would prevent express-rate-limit from accurately identifying users.`
);
}
},
/**
* Ensures totalHits value from store is a positive integer.
*
* @param hits {any} - The `totalHits` returned by the store.
*/
positiveHits(hits) {
if (typeof hits !== "number" || hits < 1 || hits !== Math.round(hits)) {
throw new ValidationError(
"ERR_ERL_INVALID_HITS",
`The totalHits value returned from the store must be a positive integer, got ${hits}`
);
}
},
/**
* Ensures a single store instance is not used with multiple express-rate-limit instances
*/
unsharedStore(store) {
if (usedStores.has(store)) {
const maybeUniquePrefix = store?.localKeys ? "" : " (with a unique prefix)";
throw new ValidationError(
"ERR_ERL_STORE_REUSE",
`A Store instance must not be shared across multiple rate limiters. Create a new instance of ${store.constructor.name}${maybeUniquePrefix} for each limiter instead.`
);
}
usedStores.add(store);
},
/**
* Ensures a given key is incremented only once per request.
*
* @param request {Request} - The Express request object.
* @param store {Store} - The store class.
* @param key {string} - The key used to store the client's hit count.
*
* @returns {void}
*/
singleCount(request, store, key) {
let storeKeys = singleCountKeys.get(request);
if (!storeKeys) {
storeKeys = /* @__PURE__ */ new Map();
singleCountKeys.set(request, storeKeys);
}
const storeKey = store.localKeys ? store : store.constructor.name;
let keys = storeKeys.get(storeKey);
if (!keys) {
keys = [];
storeKeys.set(storeKey, keys);
}
const prefixedKey = `${store.prefix ?? ""}${key}`;
if (keys.includes(prefixedKey)) {
throw new ValidationError(
"ERR_ERL_DOUBLE_COUNT",
`The hit count for ${key} was incremented more than once for a single request.`
);
}
keys.push(prefixedKey);
},
/**
* Warns the user that the behaviour for `max: 0` / `limit: 0` is
* changing in the next major release.
*
* @param limit {number} - The maximum number of hits per client.
*
* @returns {void}
*/
limit(limit) {
if (limit === 0) {
throw new ChangeWarning(
"WRN_ERL_MAX_ZERO",
`Setting limit or max to 0 disables rate limiting in express-rate-limit v6 and older, but will cause all requests to be blocked in v7`
);
}
},
/**
* Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
* and will be removed in the next major release.
*
* @param draft_polli_ratelimit_headers {any | undefined} - The now-deprecated setting that was used to enable standard headers.
*
* @returns {void}
*/
draftPolliHeaders(draft_polli_ratelimit_headers) {
if (draft_polli_ratelimit_headers) {
throw new ChangeWarning(
"WRN_ERL_DEPRECATED_DRAFT_POLLI_HEADERS",
`The draft_polli_ratelimit_headers configuration option is deprecated and has been removed in express-rate-limit v7, please set standardHeaders: 'draft-6' instead.`
);
}
},
/**
* Warns the user that the `onLimitReached` option is deprecated and
* will be removed in the next major release.
*
* @param onLimitReached {any | undefined} - The maximum number of hits per client.
*
* @returns {void}
*/
onLimitReached(onLimitReached) {
if (onLimitReached) {
throw new ChangeWarning(
"WRN_ERL_DEPRECATED_ON_LIMIT_REACHED",
`The onLimitReached configuration option is deprecated and has been removed in express-rate-limit v7.`
);
}
},
/**
* Warns the user when an invalid/unsupported version of the draft spec is passed.
*
* @param version {any | undefined} - The version passed by the user.
*
* @returns {void}
*/
headersDraftVersion(version) {
if (typeof version !== "string" || !SUPPORTED_DRAFT_VERSIONS.includes(version)) {
const versionString = SUPPORTED_DRAFT_VERSIONS.join(", ");
throw new ValidationError(
"ERR_ERL_HEADERS_UNSUPPORTED_DRAFT_VERSION",
`standardHeaders: only the following versions of the IETF draft specification are supported: ${versionString}.`
);
}
},
/**
* Warns the user when the selected headers option requires a reset time but
* the store does not provide one.
*
* @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.
*
* @returns {void}
*/
headersResetTime(resetTime) {
if (!resetTime) {
throw new ValidationError(
"ERR_ERL_HEADERS_NO_RESET",
`standardHeaders: 'draft-7' requires a 'resetTime', but the store did not provide one. The 'windowMs' value will be used instead, which may cause clients to wait longer than necessary.`
);
}
},
/**
* Checks the options.validate setting to ensure that only recognized
* validations are enabled or disabled.
*
* If any unrecognized values are found, an error is logged that
* includes the list of supported vaidations.
*/
validationsConfig() {
const supportedValidations = Object.keys(this).filter(
(k) => !["enabled", "disable"].includes(k)
);
supportedValidations.push("default");
for (const key of Object.keys(this.enabled)) {
if (!supportedValidations.includes(key)) {
throw new ValidationError(
"ERR_ERL_UNKNOWN_VALIDATION",
`options.validate.${key} is not recognized. Supported validate options are: ${supportedValidations.join(
", "
)}.`
);
}
}
},
/**
* Checks to see if the instance was created inside of a request handler,
* which would prevent it from working correctly, with the default memory
* store (or any other store with localKeys.)
*/
creationStack(store) {
const { stack } = new Error(
"express-rate-limit validation check (set options.validate.creationStack=false to disable)"
);
if (stack?.includes("Layer.handle [as handle_request]")) {
if (!store.localKeys) {
throw new ValidationError(
"ERR_ERL_CREATED_IN_REQUEST_HANDLER",
"express-rate-limit instance should *usually* be created at app initialization, not when responding to a request."
);
}
throw new ValidationError(
"ERR_ERL_CREATED_IN_REQUEST_HANDLER",
`express-rate-limit instance should be created at app initialization, not when responding to a request.`
);
}
}
};
var getValidations = (_enabled) => {
let enabled;
if (typeof _enabled === "boolean") {
enabled = {
default: _enabled
};
} else {
enabled = {
default: true,
..._enabled
};
}
const wrappedValidations = {
enabled
};
for (const [name, validation] of Object.entries(validations)) {
if (typeof validation === "function")
wrappedValidations[name] = (...args) => {
if (!(enabled[name] ?? enabled.default)) {
return;
}
try {
;
validation.apply(
wrappedValidations,
args
);
} catch (error) {
if (error instanceof ChangeWarning)
console.warn(error);
else
console.error(error);
}
};
}
return wrappedValidations;
};
// source/memory-store.ts
var MemoryStore = class {
constructor() {
/**
* These two maps store usage (requests) and reset time by key (for example, IP
* addresses or API keys).
*
* They are split into two to avoid having to iterate through the entire set to
* determine which ones need reset. Instead, `Client`s are moved from `previous`
* to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
* left in `previous`, i.e., those that have not made any recent requests, are
* known to be expired and can be deleted in bulk.
*/
this.previous = /* @__PURE__ */ new Map();
this.current = /* @__PURE__ */ new Map();
/**
* Confirmation that the keys incremented in once instance of MemoryStore
* cannot affect other instances.
*/
this.localKeys = true;
}
/**
* Method that initializes the store.
*
* @param options {Options} - The options used to setup the middleware.
*/
init(options) {
this.windowMs = options.windowMs;
if (this.interval)
clearInterval(this.interval);
this.interval = setInterval(() => {
this.clearExpired();
}, this.windowMs);
if (this.interval.unref)
this.interval.unref();
}
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
*
* @public
*/
async get(key) {
return this.current.get(key) ?? this.previous.get(key);
}
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*
* @public
*/
async increment(key) {
const client = this.getClient(key);
const now = Date.now();
if (client.resetTime.getTime() <= now) {
this.resetClient(client, now);
}
client.totalHits++;
return client;
}
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
async decrement(key) {
const client = this.getClient(key);
if (client.totalHits > 0)
client.totalHits--;
}
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
async resetKey(key) {
this.current.delete(key);
this.previous.delete(key);
}
/**
* Method to reset everyone's hit counter.
*
* @public
*/
async resetAll() {
this.current.clear();
this.previous.clear();
}
/**
* Method to stop the timer (if currently running) and prevent any memory
* leaks.
*
* @public
*/
shutdown() {
clearInterval(this.interval);
void this.resetAll();
}
/**
* Recycles a client by setting its hit count to zero, and reset time to
* `windowMs` milliseconds from now.
*
* NOT to be confused with `#resetKey()`, which removes a client from both the
* `current` and `previous` maps.
*
* @param client {Client} - The client to recycle.
* @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
*
* @return {Client} - The modified client that was passed in, to allow for chaining.
*/
resetClient(client, now = Date.now()) {
client.totalHits = 0;
client.resetTime.setTime(now + this.windowMs);
return client;
}
/**
* Retrieves or creates a client, given a key. Also ensures that the client being
* returned is in the `current` map.
*
* @param key {string} - The key under which the client is (or is to be) stored.
*
* @returns {Client} - The requested client.
*/
getClient(key) {
if (this.current.has(key))
return this.current.get(key);
let client;
if (this.previous.has(key)) {
client = this.previous.get(key);
this.previous.delete(key);
} else {
client = { totalHits: 0, resetTime: /* @__PURE__ */ new Date() };
this.resetClient(client);
}
this.current.set(key, client);
return client;
}
/**
* Move current clients to previous, create a new map for current.
*
* This function is called every `windowMs`.
*/
clearExpired() {
this.previous = this.current;
this.current = /* @__PURE__ */ new Map();
}
};
// source/lib.ts
var isLegacyStore = (store) => (
// Check that `incr` exists but `increment` does not - store authors might want
// to keep both around for backwards compatibility.
typeof store.incr === "function" && typeof store.increment !== "function"
);
var promisifyStore = (passedStore) => {
if (!isLegacyStore(passedStore)) {
return passedStore;
}
const legacyStore = passedStore;
class PromisifiedStore {
async increment(key) {
return new Promise((resolve, reject) => {
legacyStore.incr(
key,
(error, totalHits, resetTime) => {
if (error)
reject(error);
resolve({ totalHits, resetTime });
}
);
});
}
async decrement(key) {
return legacyStore.decrement(key);
}
async resetKey(key) {
return legacyStore.resetKey(key);
}
/* istanbul ignore next */
async resetAll() {
if (typeof legacyStore.resetAll === "function")
return legacyStore.resetAll();
}
}
return new PromisifiedStore();
};
var getOptionsFromConfig = (config) => {
const { validations: validations2, ...directlyPassableEntries } = config;
return {
...directlyPassableEntries,
validate: validations2.enabled
};
};
var omitUndefinedOptions = (passedOptions) => {
const omittedOptions = {};
for (const k of Object.keys(passedOptions)) {
const key = k;
if (passedOptions[key] !== void 0) {
omittedOptions[key] = passedOptions[key];
}
}
return omittedOptions;
};
var parseOptions = (passedOptions) => {
const notUndefinedOptions = omitUndefinedOptions(passedOptions);
const validations2 = getValidations(notUndefinedOptions?.validate ?? true);
validations2.validationsConfig();
validations2.draftPolliHeaders(
// @ts-expect-error see the note above.
notUndefinedOptions.draft_polli_ratelimit_headers
);
validations2.onLimitReached(notUndefinedOptions.onLimitReached);
let standardHeaders = notUndefinedOptions.standardHeaders ?? false;
if (standardHeaders === true)
standardHeaders = "draft-6";
const config = {
windowMs: 60 * 1e3,
limit: passedOptions.max ?? 5,
// `max` is deprecated, but support it anyways.
message: "Too many requests, please try again later.",
statusCode: 429,
legacyHeaders: passedOptions.headers ?? true,
identifier(request, _response) {
let duration = "";
const property = config.requestPropertyName;
const { limit } = request[property];
const seconds = config.windowMs / 1e3;
const minutes = config.windowMs / (1e3 * 60);
const hours = config.windowMs / (1e3 * 60 * 60);
const days = config.windowMs / (1e3 * 60 * 60 * 24);
if (seconds < 60)
duration = `${seconds}sec`;
else if (minutes < 60)
duration = `${minutes}min`;
else if (hours < 24)
duration = `${hours}hr${hours > 1 ? "s" : ""}`;
else
duration = `${days}day${days > 1 ? "s" : ""}`;
return `${limit}-in-${duration}`;
},
requestPropertyName: "rateLimit",
skipFailedRequests: false,
skipSuccessfulRequests: false,
requestWasSuccessful: (_request, response) => response.statusCode < 400,
skip: (_request, _response) => false,
keyGenerator(request, _response) {
validations2.ip(request.ip);
validations2.trustProxy(request);
validations2.xForwardedForHeader(request);
return request.ip;
},
async handler(request, response, _next, _optionsUsed) {
response.status(config.statusCode);
const message = typeof config.message === "function" ? await config.message(
request,
response
) : config.message;
if (!response.writableEnded) {
response.send(message);
}
},
passOnStoreError: false,
// Allow the default options to be overriden by the passed options.
...notUndefinedOptions,
// `standardHeaders` is resolved into a draft version above, use that.
standardHeaders,
// Note that this field is declared after the user's options are spread in,
// so that this field doesn't get overriden with an un-promisified store!
store: promisifyStore(notUndefinedOptions.store ?? new MemoryStore()),
// Print an error to the console if a few known misconfigurations are detected.
validations: validations2
};
if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || config.store.resetAll !== void 0 && typeof config.store.resetAll !== "function" || config.store.init !== void 0 && typeof config.store.init !== "function") {
throw new TypeError(
"An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."
);
}
return config;
};
var handleAsyncErrors = (fn) => async (request, response, next) => {
try {
await Promise.resolve(fn(request, response, next)).catch(next);
} catch (error) {
next(error);
}
};
var rateLimit = (passedOptions) => {
const config = parseOptions(passedOptions ?? {});
const options = getOptionsFromConfig(config);
config.validations.creationStack(config.store);
config.validations.unsharedStore(config.store);
if (typeof config.store.init === "function")
config.store.init(options);
const middleware = handleAsyncErrors(
async (request, response, next) => {
const skip = await config.skip(request, response);
if (skip) {
next();
return;
}
const augmentedRequest = request;
const key = await config.keyGenerator(request, response);
let totalHits = 0;
let resetTime;
try {
const incrementResult = await config.store.increment(key);
totalHits = incrementResult.totalHits;
resetTime = incrementResult.resetTime;
} catch (error) {
if (config.passOnStoreError) {
console.error(
"express-rate-limit: error from store, allowing request without rate-limiting.",
error
);
next();
return;
}
throw error;
}
config.validations.positiveHits(totalHits);
config.validations.singleCount(request, config.store, key);
const retrieveLimit = typeof config.limit === "function" ? config.limit(request, response) : config.limit;
const limit = await retrieveLimit;
config.validations.limit(limit);
const info = {
limit,
used: totalHits,
remaining: Math.max(limit - totalHits, 0),
resetTime
};
Object.defineProperty(info, "current", {
configurable: false,
enumerable: false,
value: totalHits
});
augmentedRequest[config.requestPropertyName] = info;
if (config.legacyHeaders && !response.headersSent) {
setLegacyHeaders(response, info);
}
if (config.standardHeaders && !response.headersSent) {
switch (config.standardHeaders) {
case "draft-6": {
setDraft6Headers(response, info, config.windowMs);
break;
}
case "draft-7": {
config.validations.headersResetTime(info.resetTime);
setDraft7Headers(response, info, config.windowMs);
break;
}
case "draft-8": {
const retrieveName = typeof config.identifier === "function" ? config.identifier(request, response) : config.identifier;
const name = await retrieveName;
config.validations.headersResetTime(info.resetTime);
setDraft8Headers(response, info, config.windowMs, name, key);
break;
}
default: {
config.validations.headersDraftVersion(config.standardHeaders);
break;
}
}
}
if (config.skipFailedRequests || config.skipSuccessfulRequests) {
let decremented = false;
const decrementKey = async () => {
if (!decremented) {
await config.store.decrement(key);
decremented = true;
}
};
if (config.skipFailedRequests) {
response.on("finish", async () => {
if (!await config.requestWasSuccessful(request, response))
await decrementKey();
});
response.on("close", async () => {
if (!response.writableEnded)
await decrementKey();
});
response.on("error", async () => {
await decrementKey();
});
}
if (config.skipSuccessfulRequests) {
response.on("finish", async () => {
if (await config.requestWasSuccessful(request, response))
await decrementKey();
});
}
}
config.validations.disable();
if (totalHits > limit) {
if (config.legacyHeaders || config.standardHeaders) {
setRetryAfterHeader(response, info, config.windowMs);
}
config.handler(request, response, next, options);
return;
}
next();
}
);
const getThrowFn = () => {
throw new Error("The current store does not support the get/getKey method");
};
middleware.resetKey = config.store.resetKey.bind(config.store);
middleware.getKey = typeof config.store.get === "function" ? config.store.get.bind(config.store) : getThrowFn;
return middleware;
};
var lib_default = rateLimit;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
MemoryStore,
rateLimit
});
module.exports = rateLimit; module.exports.default = rateLimit; module.exports.rateLimit = rateLimit; module.exports.MemoryStore = MemoryStore;

584
node_modules/express-rate-limit/dist/index.d.cts generated vendored Normal file
View File

@ -0,0 +1,584 @@
// Generated by dts-bundle-generator v8.0.1
import { NextFunction, Request, RequestHandler, Response } from 'express';
declare const validations: {
enabled: {
[key: string]: boolean;
};
disable(): void;
/**
* Checks whether the IP address is valid, and that it does not have a port
* number in it.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.
*
* @param ip {string | undefined} - The IP address provided by Express as request.ip.
*
* @returns {void}
*/
ip(ip: string | undefined): void;
/**
* Makes sure the trust proxy setting is not set to `true`.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.
*
* @param request {Request} - The Express request object.
*
* @returns {void}
*/
trustProxy(request: Request): void;
/**
* Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
* header is present.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.
*
* @param request {Request} - The Express request object.
*
* @returns {void}
*/
xForwardedForHeader(request: Request): void;
/**
* Ensures totalHits value from store is a positive integer.
*
* @param hits {any} - The `totalHits` returned by the store.
*/
positiveHits(hits: any): void;
/**
* Ensures a single store instance is not used with multiple express-rate-limit instances
*/
unsharedStore(store: Store): void;
/**
* Ensures a given key is incremented only once per request.
*
* @param request {Request} - The Express request object.
* @param store {Store} - The store class.
* @param key {string} - The key used to store the client's hit count.
*
* @returns {void}
*/
singleCount(request: Request, store: Store, key: string): void;
/**
* Warns the user that the behaviour for `max: 0` / `limit: 0` is
* changing in the next major release.
*
* @param limit {number} - The maximum number of hits per client.
*
* @returns {void}
*/
limit(limit: number): void;
/**
* Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
* and will be removed in the next major release.
*
* @param draft_polli_ratelimit_headers {any | undefined} - The now-deprecated setting that was used to enable standard headers.
*
* @returns {void}
*/
draftPolliHeaders(draft_polli_ratelimit_headers?: any): void;
/**
* Warns the user that the `onLimitReached` option is deprecated and
* will be removed in the next major release.
*
* @param onLimitReached {any | undefined} - The maximum number of hits per client.
*
* @returns {void}
*/
onLimitReached(onLimitReached?: any): void;
/**
* Warns the user when an invalid/unsupported version of the draft spec is passed.
*
* @param version {any | undefined} - The version passed by the user.
*
* @returns {void}
*/
headersDraftVersion(version?: any): void;
/**
* Warns the user when the selected headers option requires a reset time but
* the store does not provide one.
*
* @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.
*
* @returns {void}
*/
headersResetTime(resetTime?: Date): void;
/**
* Checks the options.validate setting to ensure that only recognized
* validations are enabled or disabled.
*
* If any unrecognized values are found, an error is logged that
* includes the list of supported vaidations.
*/
validationsConfig(): void;
/**
* Checks to see if the instance was created inside of a request handler,
* which would prevent it from working correctly, with the default memory
* store (or any other store with localKeys.)
*/
creationStack(store: Store): void;
};
export type Validations = typeof validations;
declare const SUPPORTED_DRAFT_VERSIONS: string[];
/**
* Callback that fires when a client's hit counter is incremented.
*
* @param error {Error | undefined} - The error that occurred, if any.
* @param totalHits {number} - The number of hits for that client so far.
* @param resetTime {Date | undefined} - The time when the counter resets.
*/
export type IncrementCallback = (error: Error | undefined, totalHits: number, resetTime: Date | undefined) => void;
/**
* Method (in the form of middleware) to generate/retrieve a value based on the
* incoming request.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
*
* @returns {T} - The value needed.
*/
export type ValueDeterminingMiddleware<T> = (request: Request, response: Response) => T | Promise<T>;
/**
* Express request handler that sends back a response when a client is
* rate-limited.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
* @param next {NextFunction} - The Express `next` function, can be called to skip responding.
* @param optionsUsed {Options} - The options used to set up the middleware.
*/
export type RateLimitExceededEventHandler = (request: Request, response: Response, next: NextFunction, optionsUsed: Options) => void;
/**
* Event callback that is triggered on a client's first request that exceeds the limit
* but not for subsequent requests. May be used for logging, etc. Should *not*
* send a response.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
* @param optionsUsed {Options} - The options used to set up the middleware.
*/
export type RateLimitReachedEventHandler = (request: Request, response: Response, optionsUsed: Options) => void;
/**
* Data returned from the `Store` when a client's hit counter is incremented.
*
* @property totalHits {number} - The number of hits for that client so far.
* @property resetTime {Date | undefined} - The time when the counter resets.
*/
export type ClientRateLimitInfo = {
totalHits: number;
resetTime: Date | undefined;
};
export type IncrementResponse = ClientRateLimitInfo;
/**
* A modified Express request handler with the rate limit functions.
*/
export type RateLimitRequestHandler = RequestHandler & {
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*/
getKey: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
};
/**
* An interface that all hit counter stores must implement.
*
* @deprecated 6.x - Implement the `Store` interface instead.
*/
export type LegacyStore = {
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
* @param callback {IncrementCallback} - The callback to call once the counter is incremented.
*/
incr: (key: string, callback: IncrementCallback) => void;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
decrement: (key: string) => void;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => void;
/**
* Method to reset everyone's hit counter.
*/
resetAll?: () => void;
};
/**
* An interface that all hit counter stores must implement.
*/
export type Store = {
/**
* Method that initializes the store, and has access to the options passed to
* the middleware too.
*
* @param options {Options} - The options used to setup the middleware.
*/
init?: (options: Options) => void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*/
get?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @returns {IncrementResponse | undefined} - The number of hits and reset time for that client.
*/
increment: (key: string) => Promise<IncrementResponse> | IncrementResponse;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
decrement: (key: string) => Promise<void> | void;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => Promise<void> | void;
/**
* Method to reset everyone's hit counter.
*/
resetAll?: () => Promise<void> | void;
/**
* Method to shutdown the store, stop timers, and release all resources.
*/
shutdown?: () => Promise<void> | void;
/**
* Flag to indicate that keys incremented in one instance of this store can
* not affect other instances. Typically false if a database is used, true for
* MemoryStore.
*
* Used to help detect double-counting misconfigurations.
*/
localKeys?: boolean;
/**
* Optional value that the store prepends to keys
*
* Used by the double-count check to avoid false-positives when a key is counted twice, but with different prefixes
*/
prefix?: string;
};
export type DraftHeadersVersion = (typeof SUPPORTED_DRAFT_VERSIONS)[number];
/**
* Validate configuration object for enabling or disabling specific validations.
*
* The keys must also be keys in the validations object, except `enable`, `disable`,
* and `default`.
*/
export type EnabledValidations = {
[key in keyof Omit<Validations, "enabled" | "disable"> | "default"]?: boolean;
};
/**
* The configuration options for the rate limiter.
*/
export type Options = {
/**
* How long we should remember the requests.
*
* Defaults to `60000` ms (= 1 minute).
*/
windowMs: number;
/**
* The maximum number of connections to allow during the `window` before
* rate limiting the client.
*
* Can be the limit itself as a number or express middleware that parses
* the request and then figures out the limit.
*
* Defaults to `5`.
*/
limit: number | ValueDeterminingMiddleware<number>;
/**
* The response body to send back when a client is rate limited.
*
* Defaults to `'Too many requests, please try again later.'`
*/
message: any | ValueDeterminingMiddleware<any>;
/**
* The HTTP status code to send back when a client is rate limited.
*
* Defaults to `HTTP 429 Too Many Requests` (RFC 6585).
*/
statusCode: number;
/**
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
* of requests.
*
* Defaults to `true` (for backward compatibility).
*/
legacyHeaders: boolean;
/**
* Whether to enable support for the standardized rate limit headers (`RateLimit-*`).
*
* Defaults to `false` (for backward compatibility, but its use is recommended).
*/
standardHeaders: boolean | DraftHeadersVersion;
/**
* The name used to identify the quota policy in the `RateLimit` headers as per
* the 8th draft of the IETF specification.
*
* Defaults to `{limit}-in-{window}`.
*/
identifier: string | ValueDeterminingMiddleware<string>;
/**
* The name of the property on the request object to store the rate limit info.
*
* Defaults to `rateLimit`.
*/
requestPropertyName: string;
/**
* If `true`, the library will (by default) skip all requests that have a 4XX
* or 5XX status.
*
* Defaults to `false`.
*/
skipFailedRequests: boolean;
/**
* If `true`, the library will (by default) skip all requests that have a
* status code less than 400.
*
* Defaults to `false`.
*/
skipSuccessfulRequests: boolean;
/**
* Method to generate custom identifiers for clients.
*
* By default, the client's IP address is used.
*/
keyGenerator: ValueDeterminingMiddleware<string>;
/**
* Express request handler that sends back a response when a client is
* rate-limited.
*
* By default, sends back the `statusCode` and `message` set via the options.
*/
handler: RateLimitExceededEventHandler;
/**
* Method (in the form of middleware) to determine whether or not this request
* counts towards a client's quota.
*
* By default, skips no requests.
*/
skip: ValueDeterminingMiddleware<boolean>;
/**
* Method to determine whether or not the request counts as 'succesful'. Used
* when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true.
*
* By default, requests with a response status code less than 400 are considered
* successful.
*/
requestWasSuccessful: ValueDeterminingMiddleware<boolean>;
/**
* The `Store` to use to store the hit count for each client.
*
* By default, the built-in `MemoryStore` will be used.
*/
store: Store | LegacyStore;
/**
* The list of validation checks that should run.
*/
validate: boolean | EnabledValidations;
/**
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
* of requests.
*
* @deprecated 6.x - This option was renamed to `legacyHeaders`.
*/
headers?: boolean;
/**
* The maximum number of connections to allow during the `window` before
* rate limiting the client.
*
* Can be the limit itself as a number or express middleware that parses
* the request and then figures out the limit.
*
* @deprecated 7.x - This option was renamed to `limit`. However, it will not
* be removed from the library in the foreseeable future.
*/
max?: number | ValueDeterminingMiddleware<number>;
/**
* If the Store generates an error, allow the request to pass.
*/
passOnStoreError: boolean;
};
/**
* The extended request object that includes information about the client's
* rate limit.
*/
export type AugmentedRequest = Request & {
[key: string]: RateLimitInfo;
};
/**
* The rate limit related information for each client included in the
* Express request object.
*/
export type RateLimitInfo = {
limit: number;
used: number;
remaining: number;
resetTime: Date | undefined;
};
/**
*
* Create an instance of IP rate-limiting middleware for Express.
*
* @param passedOptions {Options} - Options to configure the rate limiter.
*
* @returns {RateLimitRequestHandler} - The middleware that rate-limits clients based on your configuration.
*
* @public
*/
export declare const rateLimit: (passedOptions?: Partial<Options>) => RateLimitRequestHandler;
/**
* The record that stores information about a client - namely, how many times
* they have hit the endpoint, and when their hit count resets.
*
* Similar to `ClientRateLimitInfo`, except `resetTime` is a compulsory field.
*/
export type Client = {
totalHits: number;
resetTime: Date;
};
/**
* A `Store` that stores the hit count for each client in memory.
*
* @public
*/
export declare class MemoryStore implements Store {
/**
* The duration of time before which all hit counts are reset (in milliseconds).
*/
windowMs: number;
/**
* These two maps store usage (requests) and reset time by key (for example, IP
* addresses or API keys).
*
* They are split into two to avoid having to iterate through the entire set to
* determine which ones need reset. Instead, `Client`s are moved from `previous`
* to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
* left in `previous`, i.e., those that have not made any recent requests, are
* known to be expired and can be deleted in bulk.
*/
previous: Map<string, Client>;
current: Map<string, Client>;
/**
* A reference to the active timer.
*/
interval?: NodeJS.Timeout;
/**
* Confirmation that the keys incremented in once instance of MemoryStore
* cannot affect other instances.
*/
localKeys: boolean;
/**
* Method that initializes the store.
*
* @param options {Options} - The options used to setup the middleware.
*/
init(options: Options): void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
*
* @public
*/
get(key: string): Promise<ClientRateLimitInfo | undefined>;
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*
* @public
*/
increment(key: string): Promise<ClientRateLimitInfo>;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
decrement(key: string): Promise<void>;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
resetKey(key: string): Promise<void>;
/**
* Method to reset everyone's hit counter.
*
* @public
*/
resetAll(): Promise<void>;
/**
* Method to stop the timer (if currently running) and prevent any memory
* leaks.
*
* @public
*/
shutdown(): void;
/**
* Recycles a client by setting its hit count to zero, and reset time to
* `windowMs` milliseconds from now.
*
* NOT to be confused with `#resetKey()`, which removes a client from both the
* `current` and `previous` maps.
*
* @param client {Client} - The client to recycle.
* @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
*
* @return {Client} - The modified client that was passed in, to allow for chaining.
*/
private resetClient;
/**
* Retrieves or creates a client, given a key. Also ensures that the client being
* returned is in the `current` map.
*
* @param key {string} - The key under which the client is (or is to be) stored.
*
* @returns {Client} - The requested client.
*/
private getClient;
/**
* Move current clients to previous, create a new map for current.
*
* This function is called every `windowMs`.
*/
private clearExpired;
}
export {
rateLimit as default,
};
export {};

584
node_modules/express-rate-limit/dist/index.d.mts generated vendored Normal file
View File

@ -0,0 +1,584 @@
// Generated by dts-bundle-generator v8.0.1
import { NextFunction, Request, RequestHandler, Response } from 'express';
declare const validations: {
enabled: {
[key: string]: boolean;
};
disable(): void;
/**
* Checks whether the IP address is valid, and that it does not have a port
* number in it.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.
*
* @param ip {string | undefined} - The IP address provided by Express as request.ip.
*
* @returns {void}
*/
ip(ip: string | undefined): void;
/**
* Makes sure the trust proxy setting is not set to `true`.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.
*
* @param request {Request} - The Express request object.
*
* @returns {void}
*/
trustProxy(request: Request): void;
/**
* Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
* header is present.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.
*
* @param request {Request} - The Express request object.
*
* @returns {void}
*/
xForwardedForHeader(request: Request): void;
/**
* Ensures totalHits value from store is a positive integer.
*
* @param hits {any} - The `totalHits` returned by the store.
*/
positiveHits(hits: any): void;
/**
* Ensures a single store instance is not used with multiple express-rate-limit instances
*/
unsharedStore(store: Store): void;
/**
* Ensures a given key is incremented only once per request.
*
* @param request {Request} - The Express request object.
* @param store {Store} - The store class.
* @param key {string} - The key used to store the client's hit count.
*
* @returns {void}
*/
singleCount(request: Request, store: Store, key: string): void;
/**
* Warns the user that the behaviour for `max: 0` / `limit: 0` is
* changing in the next major release.
*
* @param limit {number} - The maximum number of hits per client.
*
* @returns {void}
*/
limit(limit: number): void;
/**
* Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
* and will be removed in the next major release.
*
* @param draft_polli_ratelimit_headers {any | undefined} - The now-deprecated setting that was used to enable standard headers.
*
* @returns {void}
*/
draftPolliHeaders(draft_polli_ratelimit_headers?: any): void;
/**
* Warns the user that the `onLimitReached` option is deprecated and
* will be removed in the next major release.
*
* @param onLimitReached {any | undefined} - The maximum number of hits per client.
*
* @returns {void}
*/
onLimitReached(onLimitReached?: any): void;
/**
* Warns the user when an invalid/unsupported version of the draft spec is passed.
*
* @param version {any | undefined} - The version passed by the user.
*
* @returns {void}
*/
headersDraftVersion(version?: any): void;
/**
* Warns the user when the selected headers option requires a reset time but
* the store does not provide one.
*
* @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.
*
* @returns {void}
*/
headersResetTime(resetTime?: Date): void;
/**
* Checks the options.validate setting to ensure that only recognized
* validations are enabled or disabled.
*
* If any unrecognized values are found, an error is logged that
* includes the list of supported vaidations.
*/
validationsConfig(): void;
/**
* Checks to see if the instance was created inside of a request handler,
* which would prevent it from working correctly, with the default memory
* store (or any other store with localKeys.)
*/
creationStack(store: Store): void;
};
export type Validations = typeof validations;
declare const SUPPORTED_DRAFT_VERSIONS: string[];
/**
* Callback that fires when a client's hit counter is incremented.
*
* @param error {Error | undefined} - The error that occurred, if any.
* @param totalHits {number} - The number of hits for that client so far.
* @param resetTime {Date | undefined} - The time when the counter resets.
*/
export type IncrementCallback = (error: Error | undefined, totalHits: number, resetTime: Date | undefined) => void;
/**
* Method (in the form of middleware) to generate/retrieve a value based on the
* incoming request.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
*
* @returns {T} - The value needed.
*/
export type ValueDeterminingMiddleware<T> = (request: Request, response: Response) => T | Promise<T>;
/**
* Express request handler that sends back a response when a client is
* rate-limited.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
* @param next {NextFunction} - The Express `next` function, can be called to skip responding.
* @param optionsUsed {Options} - The options used to set up the middleware.
*/
export type RateLimitExceededEventHandler = (request: Request, response: Response, next: NextFunction, optionsUsed: Options) => void;
/**
* Event callback that is triggered on a client's first request that exceeds the limit
* but not for subsequent requests. May be used for logging, etc. Should *not*
* send a response.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
* @param optionsUsed {Options} - The options used to set up the middleware.
*/
export type RateLimitReachedEventHandler = (request: Request, response: Response, optionsUsed: Options) => void;
/**
* Data returned from the `Store` when a client's hit counter is incremented.
*
* @property totalHits {number} - The number of hits for that client so far.
* @property resetTime {Date | undefined} - The time when the counter resets.
*/
export type ClientRateLimitInfo = {
totalHits: number;
resetTime: Date | undefined;
};
export type IncrementResponse = ClientRateLimitInfo;
/**
* A modified Express request handler with the rate limit functions.
*/
export type RateLimitRequestHandler = RequestHandler & {
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*/
getKey: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
};
/**
* An interface that all hit counter stores must implement.
*
* @deprecated 6.x - Implement the `Store` interface instead.
*/
export type LegacyStore = {
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
* @param callback {IncrementCallback} - The callback to call once the counter is incremented.
*/
incr: (key: string, callback: IncrementCallback) => void;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
decrement: (key: string) => void;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => void;
/**
* Method to reset everyone's hit counter.
*/
resetAll?: () => void;
};
/**
* An interface that all hit counter stores must implement.
*/
export type Store = {
/**
* Method that initializes the store, and has access to the options passed to
* the middleware too.
*
* @param options {Options} - The options used to setup the middleware.
*/
init?: (options: Options) => void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*/
get?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @returns {IncrementResponse | undefined} - The number of hits and reset time for that client.
*/
increment: (key: string) => Promise<IncrementResponse> | IncrementResponse;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
decrement: (key: string) => Promise<void> | void;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => Promise<void> | void;
/**
* Method to reset everyone's hit counter.
*/
resetAll?: () => Promise<void> | void;
/**
* Method to shutdown the store, stop timers, and release all resources.
*/
shutdown?: () => Promise<void> | void;
/**
* Flag to indicate that keys incremented in one instance of this store can
* not affect other instances. Typically false if a database is used, true for
* MemoryStore.
*
* Used to help detect double-counting misconfigurations.
*/
localKeys?: boolean;
/**
* Optional value that the store prepends to keys
*
* Used by the double-count check to avoid false-positives when a key is counted twice, but with different prefixes
*/
prefix?: string;
};
export type DraftHeadersVersion = (typeof SUPPORTED_DRAFT_VERSIONS)[number];
/**
* Validate configuration object for enabling or disabling specific validations.
*
* The keys must also be keys in the validations object, except `enable`, `disable`,
* and `default`.
*/
export type EnabledValidations = {
[key in keyof Omit<Validations, "enabled" | "disable"> | "default"]?: boolean;
};
/**
* The configuration options for the rate limiter.
*/
export type Options = {
/**
* How long we should remember the requests.
*
* Defaults to `60000` ms (= 1 minute).
*/
windowMs: number;
/**
* The maximum number of connections to allow during the `window` before
* rate limiting the client.
*
* Can be the limit itself as a number or express middleware that parses
* the request and then figures out the limit.
*
* Defaults to `5`.
*/
limit: number | ValueDeterminingMiddleware<number>;
/**
* The response body to send back when a client is rate limited.
*
* Defaults to `'Too many requests, please try again later.'`
*/
message: any | ValueDeterminingMiddleware<any>;
/**
* The HTTP status code to send back when a client is rate limited.
*
* Defaults to `HTTP 429 Too Many Requests` (RFC 6585).
*/
statusCode: number;
/**
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
* of requests.
*
* Defaults to `true` (for backward compatibility).
*/
legacyHeaders: boolean;
/**
* Whether to enable support for the standardized rate limit headers (`RateLimit-*`).
*
* Defaults to `false` (for backward compatibility, but its use is recommended).
*/
standardHeaders: boolean | DraftHeadersVersion;
/**
* The name used to identify the quota policy in the `RateLimit` headers as per
* the 8th draft of the IETF specification.
*
* Defaults to `{limit}-in-{window}`.
*/
identifier: string | ValueDeterminingMiddleware<string>;
/**
* The name of the property on the request object to store the rate limit info.
*
* Defaults to `rateLimit`.
*/
requestPropertyName: string;
/**
* If `true`, the library will (by default) skip all requests that have a 4XX
* or 5XX status.
*
* Defaults to `false`.
*/
skipFailedRequests: boolean;
/**
* If `true`, the library will (by default) skip all requests that have a
* status code less than 400.
*
* Defaults to `false`.
*/
skipSuccessfulRequests: boolean;
/**
* Method to generate custom identifiers for clients.
*
* By default, the client's IP address is used.
*/
keyGenerator: ValueDeterminingMiddleware<string>;
/**
* Express request handler that sends back a response when a client is
* rate-limited.
*
* By default, sends back the `statusCode` and `message` set via the options.
*/
handler: RateLimitExceededEventHandler;
/**
* Method (in the form of middleware) to determine whether or not this request
* counts towards a client's quota.
*
* By default, skips no requests.
*/
skip: ValueDeterminingMiddleware<boolean>;
/**
* Method to determine whether or not the request counts as 'succesful'. Used
* when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true.
*
* By default, requests with a response status code less than 400 are considered
* successful.
*/
requestWasSuccessful: ValueDeterminingMiddleware<boolean>;
/**
* The `Store` to use to store the hit count for each client.
*
* By default, the built-in `MemoryStore` will be used.
*/
store: Store | LegacyStore;
/**
* The list of validation checks that should run.
*/
validate: boolean | EnabledValidations;
/**
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
* of requests.
*
* @deprecated 6.x - This option was renamed to `legacyHeaders`.
*/
headers?: boolean;
/**
* The maximum number of connections to allow during the `window` before
* rate limiting the client.
*
* Can be the limit itself as a number or express middleware that parses
* the request and then figures out the limit.
*
* @deprecated 7.x - This option was renamed to `limit`. However, it will not
* be removed from the library in the foreseeable future.
*/
max?: number | ValueDeterminingMiddleware<number>;
/**
* If the Store generates an error, allow the request to pass.
*/
passOnStoreError: boolean;
};
/**
* The extended request object that includes information about the client's
* rate limit.
*/
export type AugmentedRequest = Request & {
[key: string]: RateLimitInfo;
};
/**
* The rate limit related information for each client included in the
* Express request object.
*/
export type RateLimitInfo = {
limit: number;
used: number;
remaining: number;
resetTime: Date | undefined;
};
/**
*
* Create an instance of IP rate-limiting middleware for Express.
*
* @param passedOptions {Options} - Options to configure the rate limiter.
*
* @returns {RateLimitRequestHandler} - The middleware that rate-limits clients based on your configuration.
*
* @public
*/
export declare const rateLimit: (passedOptions?: Partial<Options>) => RateLimitRequestHandler;
/**
* The record that stores information about a client - namely, how many times
* they have hit the endpoint, and when their hit count resets.
*
* Similar to `ClientRateLimitInfo`, except `resetTime` is a compulsory field.
*/
export type Client = {
totalHits: number;
resetTime: Date;
};
/**
* A `Store` that stores the hit count for each client in memory.
*
* @public
*/
export declare class MemoryStore implements Store {
/**
* The duration of time before which all hit counts are reset (in milliseconds).
*/
windowMs: number;
/**
* These two maps store usage (requests) and reset time by key (for example, IP
* addresses or API keys).
*
* They are split into two to avoid having to iterate through the entire set to
* determine which ones need reset. Instead, `Client`s are moved from `previous`
* to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
* left in `previous`, i.e., those that have not made any recent requests, are
* known to be expired and can be deleted in bulk.
*/
previous: Map<string, Client>;
current: Map<string, Client>;
/**
* A reference to the active timer.
*/
interval?: NodeJS.Timeout;
/**
* Confirmation that the keys incremented in once instance of MemoryStore
* cannot affect other instances.
*/
localKeys: boolean;
/**
* Method that initializes the store.
*
* @param options {Options} - The options used to setup the middleware.
*/
init(options: Options): void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
*
* @public
*/
get(key: string): Promise<ClientRateLimitInfo | undefined>;
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*
* @public
*/
increment(key: string): Promise<ClientRateLimitInfo>;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
decrement(key: string): Promise<void>;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
resetKey(key: string): Promise<void>;
/**
* Method to reset everyone's hit counter.
*
* @public
*/
resetAll(): Promise<void>;
/**
* Method to stop the timer (if currently running) and prevent any memory
* leaks.
*
* @public
*/
shutdown(): void;
/**
* Recycles a client by setting its hit count to zero, and reset time to
* `windowMs` milliseconds from now.
*
* NOT to be confused with `#resetKey()`, which removes a client from both the
* `current` and `previous` maps.
*
* @param client {Client} - The client to recycle.
* @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
*
* @return {Client} - The modified client that was passed in, to allow for chaining.
*/
private resetClient;
/**
* Retrieves or creates a client, given a key. Also ensures that the client being
* returned is in the `current` map.
*
* @param key {string} - The key under which the client is (or is to be) stored.
*
* @returns {Client} - The requested client.
*/
private getClient;
/**
* Move current clients to previous, create a new map for current.
*
* This function is called every `windowMs`.
*/
private clearExpired;
}
export {
rateLimit as default,
};
export {};

584
node_modules/express-rate-limit/dist/index.d.ts generated vendored Normal file
View File

@ -0,0 +1,584 @@
// Generated by dts-bundle-generator v8.0.1
import { NextFunction, Request, RequestHandler, Response } from 'express';
declare const validations: {
enabled: {
[key: string]: boolean;
};
disable(): void;
/**
* Checks whether the IP address is valid, and that it does not have a port
* number in it.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.
*
* @param ip {string | undefined} - The IP address provided by Express as request.ip.
*
* @returns {void}
*/
ip(ip: string | undefined): void;
/**
* Makes sure the trust proxy setting is not set to `true`.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.
*
* @param request {Request} - The Express request object.
*
* @returns {void}
*/
trustProxy(request: Request): void;
/**
* Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
* header is present.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.
*
* @param request {Request} - The Express request object.
*
* @returns {void}
*/
xForwardedForHeader(request: Request): void;
/**
* Ensures totalHits value from store is a positive integer.
*
* @param hits {any} - The `totalHits` returned by the store.
*/
positiveHits(hits: any): void;
/**
* Ensures a single store instance is not used with multiple express-rate-limit instances
*/
unsharedStore(store: Store): void;
/**
* Ensures a given key is incremented only once per request.
*
* @param request {Request} - The Express request object.
* @param store {Store} - The store class.
* @param key {string} - The key used to store the client's hit count.
*
* @returns {void}
*/
singleCount(request: Request, store: Store, key: string): void;
/**
* Warns the user that the behaviour for `max: 0` / `limit: 0` is
* changing in the next major release.
*
* @param limit {number} - The maximum number of hits per client.
*
* @returns {void}
*/
limit(limit: number): void;
/**
* Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
* and will be removed in the next major release.
*
* @param draft_polli_ratelimit_headers {any | undefined} - The now-deprecated setting that was used to enable standard headers.
*
* @returns {void}
*/
draftPolliHeaders(draft_polli_ratelimit_headers?: any): void;
/**
* Warns the user that the `onLimitReached` option is deprecated and
* will be removed in the next major release.
*
* @param onLimitReached {any | undefined} - The maximum number of hits per client.
*
* @returns {void}
*/
onLimitReached(onLimitReached?: any): void;
/**
* Warns the user when an invalid/unsupported version of the draft spec is passed.
*
* @param version {any | undefined} - The version passed by the user.
*
* @returns {void}
*/
headersDraftVersion(version?: any): void;
/**
* Warns the user when the selected headers option requires a reset time but
* the store does not provide one.
*
* @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.
*
* @returns {void}
*/
headersResetTime(resetTime?: Date): void;
/**
* Checks the options.validate setting to ensure that only recognized
* validations are enabled or disabled.
*
* If any unrecognized values are found, an error is logged that
* includes the list of supported vaidations.
*/
validationsConfig(): void;
/**
* Checks to see if the instance was created inside of a request handler,
* which would prevent it from working correctly, with the default memory
* store (or any other store with localKeys.)
*/
creationStack(store: Store): void;
};
export type Validations = typeof validations;
declare const SUPPORTED_DRAFT_VERSIONS: string[];
/**
* Callback that fires when a client's hit counter is incremented.
*
* @param error {Error | undefined} - The error that occurred, if any.
* @param totalHits {number} - The number of hits for that client so far.
* @param resetTime {Date | undefined} - The time when the counter resets.
*/
export type IncrementCallback = (error: Error | undefined, totalHits: number, resetTime: Date | undefined) => void;
/**
* Method (in the form of middleware) to generate/retrieve a value based on the
* incoming request.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
*
* @returns {T} - The value needed.
*/
export type ValueDeterminingMiddleware<T> = (request: Request, response: Response) => T | Promise<T>;
/**
* Express request handler that sends back a response when a client is
* rate-limited.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
* @param next {NextFunction} - The Express `next` function, can be called to skip responding.
* @param optionsUsed {Options} - The options used to set up the middleware.
*/
export type RateLimitExceededEventHandler = (request: Request, response: Response, next: NextFunction, optionsUsed: Options) => void;
/**
* Event callback that is triggered on a client's first request that exceeds the limit
* but not for subsequent requests. May be used for logging, etc. Should *not*
* send a response.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
* @param optionsUsed {Options} - The options used to set up the middleware.
*/
export type RateLimitReachedEventHandler = (request: Request, response: Response, optionsUsed: Options) => void;
/**
* Data returned from the `Store` when a client's hit counter is incremented.
*
* @property totalHits {number} - The number of hits for that client so far.
* @property resetTime {Date | undefined} - The time when the counter resets.
*/
export type ClientRateLimitInfo = {
totalHits: number;
resetTime: Date | undefined;
};
export type IncrementResponse = ClientRateLimitInfo;
/**
* A modified Express request handler with the rate limit functions.
*/
export type RateLimitRequestHandler = RequestHandler & {
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*/
getKey: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
};
/**
* An interface that all hit counter stores must implement.
*
* @deprecated 6.x - Implement the `Store` interface instead.
*/
export type LegacyStore = {
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
* @param callback {IncrementCallback} - The callback to call once the counter is incremented.
*/
incr: (key: string, callback: IncrementCallback) => void;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
decrement: (key: string) => void;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => void;
/**
* Method to reset everyone's hit counter.
*/
resetAll?: () => void;
};
/**
* An interface that all hit counter stores must implement.
*/
export type Store = {
/**
* Method that initializes the store, and has access to the options passed to
* the middleware too.
*
* @param options {Options} - The options used to setup the middleware.
*/
init?: (options: Options) => void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*/
get?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @returns {IncrementResponse | undefined} - The number of hits and reset time for that client.
*/
increment: (key: string) => Promise<IncrementResponse> | IncrementResponse;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
decrement: (key: string) => Promise<void> | void;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => Promise<void> | void;
/**
* Method to reset everyone's hit counter.
*/
resetAll?: () => Promise<void> | void;
/**
* Method to shutdown the store, stop timers, and release all resources.
*/
shutdown?: () => Promise<void> | void;
/**
* Flag to indicate that keys incremented in one instance of this store can
* not affect other instances. Typically false if a database is used, true for
* MemoryStore.
*
* Used to help detect double-counting misconfigurations.
*/
localKeys?: boolean;
/**
* Optional value that the store prepends to keys
*
* Used by the double-count check to avoid false-positives when a key is counted twice, but with different prefixes
*/
prefix?: string;
};
export type DraftHeadersVersion = (typeof SUPPORTED_DRAFT_VERSIONS)[number];
/**
* Validate configuration object for enabling or disabling specific validations.
*
* The keys must also be keys in the validations object, except `enable`, `disable`,
* and `default`.
*/
export type EnabledValidations = {
[key in keyof Omit<Validations, "enabled" | "disable"> | "default"]?: boolean;
};
/**
* The configuration options for the rate limiter.
*/
export type Options = {
/**
* How long we should remember the requests.
*
* Defaults to `60000` ms (= 1 minute).
*/
windowMs: number;
/**
* The maximum number of connections to allow during the `window` before
* rate limiting the client.
*
* Can be the limit itself as a number or express middleware that parses
* the request and then figures out the limit.
*
* Defaults to `5`.
*/
limit: number | ValueDeterminingMiddleware<number>;
/**
* The response body to send back when a client is rate limited.
*
* Defaults to `'Too many requests, please try again later.'`
*/
message: any | ValueDeterminingMiddleware<any>;
/**
* The HTTP status code to send back when a client is rate limited.
*
* Defaults to `HTTP 429 Too Many Requests` (RFC 6585).
*/
statusCode: number;
/**
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
* of requests.
*
* Defaults to `true` (for backward compatibility).
*/
legacyHeaders: boolean;
/**
* Whether to enable support for the standardized rate limit headers (`RateLimit-*`).
*
* Defaults to `false` (for backward compatibility, but its use is recommended).
*/
standardHeaders: boolean | DraftHeadersVersion;
/**
* The name used to identify the quota policy in the `RateLimit` headers as per
* the 8th draft of the IETF specification.
*
* Defaults to `{limit}-in-{window}`.
*/
identifier: string | ValueDeterminingMiddleware<string>;
/**
* The name of the property on the request object to store the rate limit info.
*
* Defaults to `rateLimit`.
*/
requestPropertyName: string;
/**
* If `true`, the library will (by default) skip all requests that have a 4XX
* or 5XX status.
*
* Defaults to `false`.
*/
skipFailedRequests: boolean;
/**
* If `true`, the library will (by default) skip all requests that have a
* status code less than 400.
*
* Defaults to `false`.
*/
skipSuccessfulRequests: boolean;
/**
* Method to generate custom identifiers for clients.
*
* By default, the client's IP address is used.
*/
keyGenerator: ValueDeterminingMiddleware<string>;
/**
* Express request handler that sends back a response when a client is
* rate-limited.
*
* By default, sends back the `statusCode` and `message` set via the options.
*/
handler: RateLimitExceededEventHandler;
/**
* Method (in the form of middleware) to determine whether or not this request
* counts towards a client's quota.
*
* By default, skips no requests.
*/
skip: ValueDeterminingMiddleware<boolean>;
/**
* Method to determine whether or not the request counts as 'succesful'. Used
* when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true.
*
* By default, requests with a response status code less than 400 are considered
* successful.
*/
requestWasSuccessful: ValueDeterminingMiddleware<boolean>;
/**
* The `Store` to use to store the hit count for each client.
*
* By default, the built-in `MemoryStore` will be used.
*/
store: Store | LegacyStore;
/**
* The list of validation checks that should run.
*/
validate: boolean | EnabledValidations;
/**
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
* of requests.
*
* @deprecated 6.x - This option was renamed to `legacyHeaders`.
*/
headers?: boolean;
/**
* The maximum number of connections to allow during the `window` before
* rate limiting the client.
*
* Can be the limit itself as a number or express middleware that parses
* the request and then figures out the limit.
*
* @deprecated 7.x - This option was renamed to `limit`. However, it will not
* be removed from the library in the foreseeable future.
*/
max?: number | ValueDeterminingMiddleware<number>;
/**
* If the Store generates an error, allow the request to pass.
*/
passOnStoreError: boolean;
};
/**
* The extended request object that includes information about the client's
* rate limit.
*/
export type AugmentedRequest = Request & {
[key: string]: RateLimitInfo;
};
/**
* The rate limit related information for each client included in the
* Express request object.
*/
export type RateLimitInfo = {
limit: number;
used: number;
remaining: number;
resetTime: Date | undefined;
};
/**
*
* Create an instance of IP rate-limiting middleware for Express.
*
* @param passedOptions {Options} - Options to configure the rate limiter.
*
* @returns {RateLimitRequestHandler} - The middleware that rate-limits clients based on your configuration.
*
* @public
*/
export declare const rateLimit: (passedOptions?: Partial<Options>) => RateLimitRequestHandler;
/**
* The record that stores information about a client - namely, how many times
* they have hit the endpoint, and when their hit count resets.
*
* Similar to `ClientRateLimitInfo`, except `resetTime` is a compulsory field.
*/
export type Client = {
totalHits: number;
resetTime: Date;
};
/**
* A `Store` that stores the hit count for each client in memory.
*
* @public
*/
export declare class MemoryStore implements Store {
/**
* The duration of time before which all hit counts are reset (in milliseconds).
*/
windowMs: number;
/**
* These two maps store usage (requests) and reset time by key (for example, IP
* addresses or API keys).
*
* They are split into two to avoid having to iterate through the entire set to
* determine which ones need reset. Instead, `Client`s are moved from `previous`
* to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
* left in `previous`, i.e., those that have not made any recent requests, are
* known to be expired and can be deleted in bulk.
*/
previous: Map<string, Client>;
current: Map<string, Client>;
/**
* A reference to the active timer.
*/
interval?: NodeJS.Timeout;
/**
* Confirmation that the keys incremented in once instance of MemoryStore
* cannot affect other instances.
*/
localKeys: boolean;
/**
* Method that initializes the store.
*
* @param options {Options} - The options used to setup the middleware.
*/
init(options: Options): void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
*
* @public
*/
get(key: string): Promise<ClientRateLimitInfo | undefined>;
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*
* @public
*/
increment(key: string): Promise<ClientRateLimitInfo>;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
decrement(key: string): Promise<void>;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
resetKey(key: string): Promise<void>;
/**
* Method to reset everyone's hit counter.
*
* @public
*/
resetAll(): Promise<void>;
/**
* Method to stop the timer (if currently running) and prevent any memory
* leaks.
*
* @public
*/
shutdown(): void;
/**
* Recycles a client by setting its hit count to zero, and reset time to
* `windowMs` milliseconds from now.
*
* NOT to be confused with `#resetKey()`, which removes a client from both the
* `current` and `previous` maps.
*
* @param client {Client} - The client to recycle.
* @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
*
* @return {Client} - The modified client that was passed in, to allow for chaining.
*/
private resetClient;
/**
* Retrieves or creates a client, given a key. Also ensures that the client being
* returned is in the `current` map.
*
* @param key {string} - The key under which the client is (or is to be) stored.
*
* @returns {Client} - The requested client.
*/
private getClient;
/**
* Move current clients to previous, create a new map for current.
*
* This function is called every `windowMs`.
*/
private clearExpired;
}
export {
rateLimit as default,
};
export {};

809
node_modules/express-rate-limit/dist/index.mjs generated vendored Normal file
View File

@ -0,0 +1,809 @@
// source/headers.ts
import { Buffer } from "buffer";
import { createHash } from "crypto";
var SUPPORTED_DRAFT_VERSIONS = ["draft-6", "draft-7", "draft-8"];
var getResetSeconds = (resetTime, windowMs) => {
let resetSeconds = void 0;
if (resetTime) {
const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
resetSeconds = Math.max(0, deltaSeconds);
} else if (windowMs) {
resetSeconds = Math.ceil(windowMs / 1e3);
}
return resetSeconds;
};
var getPartitionKey = (key) => {
const hash = createHash("sha256");
hash.update(key);
const partitionKey = hash.digest("hex").slice(0, 12);
return Buffer.from(partitionKey).toString("base64");
};
var setLegacyHeaders = (response, info) => {
if (response.headersSent)
return;
response.setHeader("X-RateLimit-Limit", info.limit.toString());
response.setHeader("X-RateLimit-Remaining", info.remaining.toString());
if (info.resetTime instanceof Date) {
response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
response.setHeader(
"X-RateLimit-Reset",
Math.ceil(info.resetTime.getTime() / 1e3).toString()
);
}
};
var setDraft6Headers = (response, info, windowMs) => {
if (response.headersSent)
return;
const windowSeconds = Math.ceil(windowMs / 1e3);
const resetSeconds = getResetSeconds(info.resetTime);
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
response.setHeader("RateLimit-Limit", info.limit.toString());
response.setHeader("RateLimit-Remaining", info.remaining.toString());
if (resetSeconds)
response.setHeader("RateLimit-Reset", resetSeconds.toString());
};
var setDraft7Headers = (response, info, windowMs) => {
if (response.headersSent)
return;
const windowSeconds = Math.ceil(windowMs / 1e3);
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
response.setHeader(
"RateLimit",
`limit=${info.limit}, remaining=${info.remaining}, reset=${resetSeconds}`
);
};
var setDraft8Headers = (response, info, windowMs, name, key) => {
if (response.headersSent)
return;
const windowSeconds = Math.ceil(windowMs / 1e3);
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
const partitionKey = getPartitionKey(key);
const policy = `q=${info.limit}; w=${windowSeconds}; pk=:${partitionKey}:`;
const header = `r=${info.remaining}; t=${resetSeconds}`;
response.append("RateLimit-Policy", `"${name}"; ${policy}`);
response.append("RateLimit", `"${name}"; ${header}`);
};
var setRetryAfterHeader = (response, info, windowMs) => {
if (response.headersSent)
return;
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
response.setHeader("Retry-After", resetSeconds.toString());
};
// source/validations.ts
import { isIP } from "net";
var ValidationError = class extends Error {
/**
* The code must be a string, in snake case and all capital, that starts with
* the substring `ERR_ERL_`.
*
* The message must be a string, starting with an uppercase character,
* describing the issue in detail.
*/
constructor(code, message) {
const url = `https://express-rate-limit.github.io/${code}/`;
super(`${message} See ${url} for more information.`);
this.name = this.constructor.name;
this.code = code;
this.help = url;
}
};
var ChangeWarning = class extends ValidationError {
};
var usedStores = /* @__PURE__ */ new Set();
var singleCountKeys = /* @__PURE__ */ new WeakMap();
var validations = {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
enabled: {
default: true
},
// Should be EnabledValidations type, but that's a circular reference
disable() {
for (const k of Object.keys(this.enabled))
this.enabled[k] = false;
},
/**
* Checks whether the IP address is valid, and that it does not have a port
* number in it.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.
*
* @param ip {string | undefined} - The IP address provided by Express as request.ip.
*
* @returns {void}
*/
ip(ip) {
if (ip === void 0) {
throw new ValidationError(
"ERR_ERL_UNDEFINED_IP_ADDRESS",
`An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.`
);
}
if (!isIP(ip)) {
throw new ValidationError(
"ERR_ERL_INVALID_IP_ADDRESS",
`An invalid 'request.ip' (${ip}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`
);
}
},
/**
* Makes sure the trust proxy setting is not set to `true`.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.
*
* @param request {Request} - The Express request object.
*
* @returns {void}
*/
trustProxy(request) {
if (request.app.get("trust proxy") === true) {
throw new ValidationError(
"ERR_ERL_PERMISSIVE_TRUST_PROXY",
`The Express 'trust proxy' setting is true, which allows anyone to trivially bypass IP-based rate limiting.`
);
}
},
/**
* Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
* header is present.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.
*
* @param request {Request} - The Express request object.
*
* @returns {void}
*/
xForwardedForHeader(request) {
if (request.headers["x-forwarded-for"] && request.app.get("trust proxy") === false) {
throw new ValidationError(
"ERR_ERL_UNEXPECTED_X_FORWARDED_FOR",
`The 'X-Forwarded-For' header is set but the Express 'trust proxy' setting is false (default). This could indicate a misconfiguration which would prevent express-rate-limit from accurately identifying users.`
);
}
},
/**
* Ensures totalHits value from store is a positive integer.
*
* @param hits {any} - The `totalHits` returned by the store.
*/
positiveHits(hits) {
if (typeof hits !== "number" || hits < 1 || hits !== Math.round(hits)) {
throw new ValidationError(
"ERR_ERL_INVALID_HITS",
`The totalHits value returned from the store must be a positive integer, got ${hits}`
);
}
},
/**
* Ensures a single store instance is not used with multiple express-rate-limit instances
*/
unsharedStore(store) {
if (usedStores.has(store)) {
const maybeUniquePrefix = store?.localKeys ? "" : " (with a unique prefix)";
throw new ValidationError(
"ERR_ERL_STORE_REUSE",
`A Store instance must not be shared across multiple rate limiters. Create a new instance of ${store.constructor.name}${maybeUniquePrefix} for each limiter instead.`
);
}
usedStores.add(store);
},
/**
* Ensures a given key is incremented only once per request.
*
* @param request {Request} - The Express request object.
* @param store {Store} - The store class.
* @param key {string} - The key used to store the client's hit count.
*
* @returns {void}
*/
singleCount(request, store, key) {
let storeKeys = singleCountKeys.get(request);
if (!storeKeys) {
storeKeys = /* @__PURE__ */ new Map();
singleCountKeys.set(request, storeKeys);
}
const storeKey = store.localKeys ? store : store.constructor.name;
let keys = storeKeys.get(storeKey);
if (!keys) {
keys = [];
storeKeys.set(storeKey, keys);
}
const prefixedKey = `${store.prefix ?? ""}${key}`;
if (keys.includes(prefixedKey)) {
throw new ValidationError(
"ERR_ERL_DOUBLE_COUNT",
`The hit count for ${key} was incremented more than once for a single request.`
);
}
keys.push(prefixedKey);
},
/**
* Warns the user that the behaviour for `max: 0` / `limit: 0` is
* changing in the next major release.
*
* @param limit {number} - The maximum number of hits per client.
*
* @returns {void}
*/
limit(limit) {
if (limit === 0) {
throw new ChangeWarning(
"WRN_ERL_MAX_ZERO",
`Setting limit or max to 0 disables rate limiting in express-rate-limit v6 and older, but will cause all requests to be blocked in v7`
);
}
},
/**
* Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
* and will be removed in the next major release.
*
* @param draft_polli_ratelimit_headers {any | undefined} - The now-deprecated setting that was used to enable standard headers.
*
* @returns {void}
*/
draftPolliHeaders(draft_polli_ratelimit_headers) {
if (draft_polli_ratelimit_headers) {
throw new ChangeWarning(
"WRN_ERL_DEPRECATED_DRAFT_POLLI_HEADERS",
`The draft_polli_ratelimit_headers configuration option is deprecated and has been removed in express-rate-limit v7, please set standardHeaders: 'draft-6' instead.`
);
}
},
/**
* Warns the user that the `onLimitReached` option is deprecated and
* will be removed in the next major release.
*
* @param onLimitReached {any | undefined} - The maximum number of hits per client.
*
* @returns {void}
*/
onLimitReached(onLimitReached) {
if (onLimitReached) {
throw new ChangeWarning(
"WRN_ERL_DEPRECATED_ON_LIMIT_REACHED",
`The onLimitReached configuration option is deprecated and has been removed in express-rate-limit v7.`
);
}
},
/**
* Warns the user when an invalid/unsupported version of the draft spec is passed.
*
* @param version {any | undefined} - The version passed by the user.
*
* @returns {void}
*/
headersDraftVersion(version) {
if (typeof version !== "string" || !SUPPORTED_DRAFT_VERSIONS.includes(version)) {
const versionString = SUPPORTED_DRAFT_VERSIONS.join(", ");
throw new ValidationError(
"ERR_ERL_HEADERS_UNSUPPORTED_DRAFT_VERSION",
`standardHeaders: only the following versions of the IETF draft specification are supported: ${versionString}.`
);
}
},
/**
* Warns the user when the selected headers option requires a reset time but
* the store does not provide one.
*
* @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.
*
* @returns {void}
*/
headersResetTime(resetTime) {
if (!resetTime) {
throw new ValidationError(
"ERR_ERL_HEADERS_NO_RESET",
`standardHeaders: 'draft-7' requires a 'resetTime', but the store did not provide one. The 'windowMs' value will be used instead, which may cause clients to wait longer than necessary.`
);
}
},
/**
* Checks the options.validate setting to ensure that only recognized
* validations are enabled or disabled.
*
* If any unrecognized values are found, an error is logged that
* includes the list of supported vaidations.
*/
validationsConfig() {
const supportedValidations = Object.keys(this).filter(
(k) => !["enabled", "disable"].includes(k)
);
supportedValidations.push("default");
for (const key of Object.keys(this.enabled)) {
if (!supportedValidations.includes(key)) {
throw new ValidationError(
"ERR_ERL_UNKNOWN_VALIDATION",
`options.validate.${key} is not recognized. Supported validate options are: ${supportedValidations.join(
", "
)}.`
);
}
}
},
/**
* Checks to see if the instance was created inside of a request handler,
* which would prevent it from working correctly, with the default memory
* store (or any other store with localKeys.)
*/
creationStack(store) {
const { stack } = new Error(
"express-rate-limit validation check (set options.validate.creationStack=false to disable)"
);
if (stack?.includes("Layer.handle [as handle_request]")) {
if (!store.localKeys) {
throw new ValidationError(
"ERR_ERL_CREATED_IN_REQUEST_HANDLER",
"express-rate-limit instance should *usually* be created at app initialization, not when responding to a request."
);
}
throw new ValidationError(
"ERR_ERL_CREATED_IN_REQUEST_HANDLER",
`express-rate-limit instance should be created at app initialization, not when responding to a request.`
);
}
}
};
var getValidations = (_enabled) => {
let enabled;
if (typeof _enabled === "boolean") {
enabled = {
default: _enabled
};
} else {
enabled = {
default: true,
..._enabled
};
}
const wrappedValidations = {
enabled
};
for (const [name, validation] of Object.entries(validations)) {
if (typeof validation === "function")
wrappedValidations[name] = (...args) => {
if (!(enabled[name] ?? enabled.default)) {
return;
}
try {
;
validation.apply(
wrappedValidations,
args
);
} catch (error) {
if (error instanceof ChangeWarning)
console.warn(error);
else
console.error(error);
}
};
}
return wrappedValidations;
};
// source/memory-store.ts
var MemoryStore = class {
constructor() {
/**
* These two maps store usage (requests) and reset time by key (for example, IP
* addresses or API keys).
*
* They are split into two to avoid having to iterate through the entire set to
* determine which ones need reset. Instead, `Client`s are moved from `previous`
* to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
* left in `previous`, i.e., those that have not made any recent requests, are
* known to be expired and can be deleted in bulk.
*/
this.previous = /* @__PURE__ */ new Map();
this.current = /* @__PURE__ */ new Map();
/**
* Confirmation that the keys incremented in once instance of MemoryStore
* cannot affect other instances.
*/
this.localKeys = true;
}
/**
* Method that initializes the store.
*
* @param options {Options} - The options used to setup the middleware.
*/
init(options) {
this.windowMs = options.windowMs;
if (this.interval)
clearInterval(this.interval);
this.interval = setInterval(() => {
this.clearExpired();
}, this.windowMs);
if (this.interval.unref)
this.interval.unref();
}
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
*
* @public
*/
async get(key) {
return this.current.get(key) ?? this.previous.get(key);
}
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*
* @public
*/
async increment(key) {
const client = this.getClient(key);
const now = Date.now();
if (client.resetTime.getTime() <= now) {
this.resetClient(client, now);
}
client.totalHits++;
return client;
}
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
async decrement(key) {
const client = this.getClient(key);
if (client.totalHits > 0)
client.totalHits--;
}
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
async resetKey(key) {
this.current.delete(key);
this.previous.delete(key);
}
/**
* Method to reset everyone's hit counter.
*
* @public
*/
async resetAll() {
this.current.clear();
this.previous.clear();
}
/**
* Method to stop the timer (if currently running) and prevent any memory
* leaks.
*
* @public
*/
shutdown() {
clearInterval(this.interval);
void this.resetAll();
}
/**
* Recycles a client by setting its hit count to zero, and reset time to
* `windowMs` milliseconds from now.
*
* NOT to be confused with `#resetKey()`, which removes a client from both the
* `current` and `previous` maps.
*
* @param client {Client} - The client to recycle.
* @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
*
* @return {Client} - The modified client that was passed in, to allow for chaining.
*/
resetClient(client, now = Date.now()) {
client.totalHits = 0;
client.resetTime.setTime(now + this.windowMs);
return client;
}
/**
* Retrieves or creates a client, given a key. Also ensures that the client being
* returned is in the `current` map.
*
* @param key {string} - The key under which the client is (or is to be) stored.
*
* @returns {Client} - The requested client.
*/
getClient(key) {
if (this.current.has(key))
return this.current.get(key);
let client;
if (this.previous.has(key)) {
client = this.previous.get(key);
this.previous.delete(key);
} else {
client = { totalHits: 0, resetTime: /* @__PURE__ */ new Date() };
this.resetClient(client);
}
this.current.set(key, client);
return client;
}
/**
* Move current clients to previous, create a new map for current.
*
* This function is called every `windowMs`.
*/
clearExpired() {
this.previous = this.current;
this.current = /* @__PURE__ */ new Map();
}
};
// source/lib.ts
var isLegacyStore = (store) => (
// Check that `incr` exists but `increment` does not - store authors might want
// to keep both around for backwards compatibility.
typeof store.incr === "function" && typeof store.increment !== "function"
);
var promisifyStore = (passedStore) => {
if (!isLegacyStore(passedStore)) {
return passedStore;
}
const legacyStore = passedStore;
class PromisifiedStore {
async increment(key) {
return new Promise((resolve, reject) => {
legacyStore.incr(
key,
(error, totalHits, resetTime) => {
if (error)
reject(error);
resolve({ totalHits, resetTime });
}
);
});
}
async decrement(key) {
return legacyStore.decrement(key);
}
async resetKey(key) {
return legacyStore.resetKey(key);
}
/* istanbul ignore next */
async resetAll() {
if (typeof legacyStore.resetAll === "function")
return legacyStore.resetAll();
}
}
return new PromisifiedStore();
};
var getOptionsFromConfig = (config) => {
const { validations: validations2, ...directlyPassableEntries } = config;
return {
...directlyPassableEntries,
validate: validations2.enabled
};
};
var omitUndefinedOptions = (passedOptions) => {
const omittedOptions = {};
for (const k of Object.keys(passedOptions)) {
const key = k;
if (passedOptions[key] !== void 0) {
omittedOptions[key] = passedOptions[key];
}
}
return omittedOptions;
};
var parseOptions = (passedOptions) => {
const notUndefinedOptions = omitUndefinedOptions(passedOptions);
const validations2 = getValidations(notUndefinedOptions?.validate ?? true);
validations2.validationsConfig();
validations2.draftPolliHeaders(
// @ts-expect-error see the note above.
notUndefinedOptions.draft_polli_ratelimit_headers
);
validations2.onLimitReached(notUndefinedOptions.onLimitReached);
let standardHeaders = notUndefinedOptions.standardHeaders ?? false;
if (standardHeaders === true)
standardHeaders = "draft-6";
const config = {
windowMs: 60 * 1e3,
limit: passedOptions.max ?? 5,
// `max` is deprecated, but support it anyways.
message: "Too many requests, please try again later.",
statusCode: 429,
legacyHeaders: passedOptions.headers ?? true,
identifier(request, _response) {
let duration = "";
const property = config.requestPropertyName;
const { limit } = request[property];
const seconds = config.windowMs / 1e3;
const minutes = config.windowMs / (1e3 * 60);
const hours = config.windowMs / (1e3 * 60 * 60);
const days = config.windowMs / (1e3 * 60 * 60 * 24);
if (seconds < 60)
duration = `${seconds}sec`;
else if (minutes < 60)
duration = `${minutes}min`;
else if (hours < 24)
duration = `${hours}hr${hours > 1 ? "s" : ""}`;
else
duration = `${days}day${days > 1 ? "s" : ""}`;
return `${limit}-in-${duration}`;
},
requestPropertyName: "rateLimit",
skipFailedRequests: false,
skipSuccessfulRequests: false,
requestWasSuccessful: (_request, response) => response.statusCode < 400,
skip: (_request, _response) => false,
keyGenerator(request, _response) {
validations2.ip(request.ip);
validations2.trustProxy(request);
validations2.xForwardedForHeader(request);
return request.ip;
},
async handler(request, response, _next, _optionsUsed) {
response.status(config.statusCode);
const message = typeof config.message === "function" ? await config.message(
request,
response
) : config.message;
if (!response.writableEnded) {
response.send(message);
}
},
passOnStoreError: false,
// Allow the default options to be overriden by the passed options.
...notUndefinedOptions,
// `standardHeaders` is resolved into a draft version above, use that.
standardHeaders,
// Note that this field is declared after the user's options are spread in,
// so that this field doesn't get overriden with an un-promisified store!
store: promisifyStore(notUndefinedOptions.store ?? new MemoryStore()),
// Print an error to the console if a few known misconfigurations are detected.
validations: validations2
};
if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || config.store.resetAll !== void 0 && typeof config.store.resetAll !== "function" || config.store.init !== void 0 && typeof config.store.init !== "function") {
throw new TypeError(
"An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."
);
}
return config;
};
var handleAsyncErrors = (fn) => async (request, response, next) => {
try {
await Promise.resolve(fn(request, response, next)).catch(next);
} catch (error) {
next(error);
}
};
var rateLimit = (passedOptions) => {
const config = parseOptions(passedOptions ?? {});
const options = getOptionsFromConfig(config);
config.validations.creationStack(config.store);
config.validations.unsharedStore(config.store);
if (typeof config.store.init === "function")
config.store.init(options);
const middleware = handleAsyncErrors(
async (request, response, next) => {
const skip = await config.skip(request, response);
if (skip) {
next();
return;
}
const augmentedRequest = request;
const key = await config.keyGenerator(request, response);
let totalHits = 0;
let resetTime;
try {
const incrementResult = await config.store.increment(key);
totalHits = incrementResult.totalHits;
resetTime = incrementResult.resetTime;
} catch (error) {
if (config.passOnStoreError) {
console.error(
"express-rate-limit: error from store, allowing request without rate-limiting.",
error
);
next();
return;
}
throw error;
}
config.validations.positiveHits(totalHits);
config.validations.singleCount(request, config.store, key);
const retrieveLimit = typeof config.limit === "function" ? config.limit(request, response) : config.limit;
const limit = await retrieveLimit;
config.validations.limit(limit);
const info = {
limit,
used: totalHits,
remaining: Math.max(limit - totalHits, 0),
resetTime
};
Object.defineProperty(info, "current", {
configurable: false,
enumerable: false,
value: totalHits
});
augmentedRequest[config.requestPropertyName] = info;
if (config.legacyHeaders && !response.headersSent) {
setLegacyHeaders(response, info);
}
if (config.standardHeaders && !response.headersSent) {
switch (config.standardHeaders) {
case "draft-6": {
setDraft6Headers(response, info, config.windowMs);
break;
}
case "draft-7": {
config.validations.headersResetTime(info.resetTime);
setDraft7Headers(response, info, config.windowMs);
break;
}
case "draft-8": {
const retrieveName = typeof config.identifier === "function" ? config.identifier(request, response) : config.identifier;
const name = await retrieveName;
config.validations.headersResetTime(info.resetTime);
setDraft8Headers(response, info, config.windowMs, name, key);
break;
}
default: {
config.validations.headersDraftVersion(config.standardHeaders);
break;
}
}
}
if (config.skipFailedRequests || config.skipSuccessfulRequests) {
let decremented = false;
const decrementKey = async () => {
if (!decremented) {
await config.store.decrement(key);
decremented = true;
}
};
if (config.skipFailedRequests) {
response.on("finish", async () => {
if (!await config.requestWasSuccessful(request, response))
await decrementKey();
});
response.on("close", async () => {
if (!response.writableEnded)
await decrementKey();
});
response.on("error", async () => {
await decrementKey();
});
}
if (config.skipSuccessfulRequests) {
response.on("finish", async () => {
if (await config.requestWasSuccessful(request, response))
await decrementKey();
});
}
}
config.validations.disable();
if (totalHits > limit) {
if (config.legacyHeaders || config.standardHeaders) {
setRetryAfterHeader(response, info, config.windowMs);
}
config.handler(request, response, next, options);
return;
}
next();
}
);
const getThrowFn = () => {
throw new Error("The current store does not support the get/getKey method");
};
middleware.resetKey = config.store.resetKey.bind(config.store);
middleware.getKey = typeof config.store.get === "function" ? config.store.get.bind(config.store) : getThrowFn;
return middleware;
};
var lib_default = rateLimit;
export {
MemoryStore,
lib_default as default,
lib_default as rateLimit
};

20
node_modules/express-rate-limit/license.md generated vendored Normal file
View File

@ -0,0 +1,20 @@
# MIT License
Copyright 2023 Nathan Friedly, Vedant K
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

133
node_modules/express-rate-limit/package.json generated vendored Normal file
View File

@ -0,0 +1,133 @@
{
"name": "express-rate-limit",
"version": "7.5.0",
"description": "Basic IP rate-limiting middleware for Express. Use to limit repeated requests to public APIs and/or endpoints such as password reset.",
"author": {
"name": "Nathan Friedly",
"url": "http://nfriedly.com/"
},
"license": "MIT",
"homepage": "https://github.com/express-rate-limit/express-rate-limit",
"repository": {
"type": "git",
"url": "git+https://github.com/express-rate-limit/express-rate-limit.git"
},
"funding": "https://github.com/sponsors/express-rate-limit",
"keywords": [
"express-rate-limit",
"express",
"rate",
"limit",
"ratelimit",
"rate-limit",
"middleware",
"ip",
"auth",
"authorization",
"security",
"brute",
"force",
"bruteforce",
"brute-force",
"attack"
],
"type": "module",
"exports": {
".": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist/",
"tsconfig.json"
],
"engines": {
"node": ">= 16"
},
"scripts": {
"clean": "del-cli dist/ coverage/ *.log *.tmp *.bak *.tgz",
"build:cjs": "esbuild --platform=node --bundle --target=es2022 --format=cjs --outfile=dist/index.cjs --footer:js=\"module.exports = rateLimit; module.exports.default = rateLimit; module.exports.rateLimit = rateLimit; module.exports.MemoryStore = MemoryStore;\" source/index.ts",
"build:esm": "esbuild --platform=node --bundle --target=es2022 --format=esm --outfile=dist/index.mjs source/index.ts",
"build:types": "dts-bundle-generator --out-file=dist/index.d.ts source/index.ts && cp dist/index.d.ts dist/index.d.cts && cp dist/index.d.ts dist/index.d.mts",
"compile": "run-s clean build:*",
"docs": "cd docs && mintlify dev",
"lint:code": "xo",
"lint:rest": "prettier --check .",
"lint": "run-s lint:*",
"format:code": "xo --fix",
"format:rest": "prettier --write .",
"format": "run-s format:*",
"test:lib": "jest",
"test:ext": "cd test/external/ && bash run-all-tests",
"test": "run-s lint test:lib",
"pre-commit": "lint-staged",
"prepare": "run-s compile && husky install config/husky"
},
"peerDependencies": {
"express": "^4.11 || 5 || ^5.0.0-beta.1"
},
"devDependencies": {
"@express-rate-limit/prettier": "1.1.1",
"@express-rate-limit/tsconfig": "1.0.2",
"@jest/globals": "29.7.0",
"@types/express": "4.17.20",
"@types/jest": "29.5.6",
"@types/node": "20.8.7",
"@types/supertest": "2.0.15",
"del-cli": "5.1.0",
"dts-bundle-generator": "8.0.1",
"esbuild": "0.19.5",
"express": "4.21.1",
"husky": "8.0.3",
"jest": "29.7.0",
"lint-staged": "15.0.2",
"mintlify": "4.0.63",
"npm-run-all": "4.1.5",
"ratelimit-header-parser": "0.1.0",
"supertest": "6.3.3",
"ts-jest": "29.1.1",
"ts-node": "10.9.1",
"typescript": "5.2.2",
"xo": "0.56.0"
},
"xo": {
"prettier": true,
"rules": {
"@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/no-dynamic-delete": 0,
"@typescript-eslint/no-confusing-void-expression": 0,
"@typescript-eslint/consistent-indexed-object-style": [
"error",
"index-signature"
],
"n/no-unsupported-features/es-syntax": 0
},
"overrides": [
{
"files": "test/library/*.ts",
"rules": {
"@typescript-eslint/no-unsafe-argument": 0,
"@typescript-eslint/no-unsafe-assignment": 0
}
}
],
"ignore": [
"test/external"
]
},
"prettier": "@express-rate-limit/prettier",
"lint-staged": {
"{source,test}/**/*.ts": "xo --fix",
"**/*.{json,yaml,md}": "prettier --write "
}
}

147
node_modules/express-rate-limit/readme.md generated vendored Normal file
View File

@ -0,0 +1,147 @@
<h1 align="center"> <code>express-rate-limit</code> </h1>
<div align="center">
[![tests](https://img.shields.io/github/actions/workflow/status/express-rate-limit/express-rate-limit/ci.yaml)](https://github.com/express-rate-limit/express-rate-limit/actions/workflows/ci.yaml)
[![npm version](https://img.shields.io/npm/v/express-rate-limit.svg)](https://npmjs.org/package/express-rate-limit 'View this project on NPM')
[![npm downloads](https://img.shields.io/npm/dm/express-rate-limit)](https://www.npmjs.com/package/express-rate-limit)
[![license](https://img.shields.io/npm/l/express-rate-limit)](license.md)
</div>
Basic rate-limiting middleware for [Express](http://expressjs.com/). Use to
limit repeated requests to public APIs and/or endpoints such as password reset.
Plays nice with
[express-slow-down](https://www.npmjs.com/package/express-slow-down) and
[ratelimit-header-parser](https://www.npmjs.com/package/ratelimit-header-parser).
## Usage
The [full documentation](https://express-rate-limit.mintlify.app/overview) is
available on-line.
```ts
import { rateLimit } from 'express-rate-limit'
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
limit: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes).
standardHeaders: 'draft-8', // draft-6: `RateLimit-*` headers; draft-7 & draft-8: combined `RateLimit` header
legacyHeaders: false, // Disable the `X-RateLimit-*` headers.
// store: ... , // Redis, Memcached, etc. See below.
})
// Apply the rate limiting middleware to all requests.
app.use(limiter)
```
### Data Stores
The rate limiter comes with a built-in memory store, and supports a variety of
[external data stores](https://express-rate-limit.mintlify.app/reference/stores).
### Configuration
All function options may be async. Click the name for additional info and
default values.
| Option | Type | Remarks |
| -------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------- |
| [`windowMs`] | `number` | How long to remember requests for, in milliseconds. |
| [`limit`] | `number` \| `function` | How many requests to allow. |
| [`message`] | `string` \| `json` \| `function` | Response to return after limit is reached. |
| [`statusCode`] | `number` | HTTP status code after limit is reached (default is 429). |
| [`handler`] | `function` | Function to run after limit is reached (overrides `message` and `statusCode` settings, if set). |
| [`legacyHeaders`] | `boolean` | Enable the `X-Rate-Limit` header. |
| [`standardHeaders`] | `'draft-6'` \| `'draft-7'` \| `'draft-8'` | Enable the `Ratelimit` header. |
| [`identifier`] | `string` \| `function` | Name associated with the quota policy enforced by this rate limiter. |
| [`store`] | `Store` | Use a custom store to share hit counts across multiple nodes. |
| [`passOnStoreError`] | `boolean` | Allow (`true`) or block (`false`, default) traffic if the store becomes unavailable. |
| [`keyGenerator`] | `function` | Identify users (defaults to IP address). |
| [`requestPropertyName`] | `string` | Add rate limit info to the `req` object. |
| [`skip`] | `function` | Return `true` to bypass the limiter for the given request. |
| [`skipSuccessfulRequests`] | `boolean` | Uncount 1xx/2xx/3xx responses. |
| [`skipFailedRequests`] | `boolean` | Uncount 4xx/5xx responses. |
| [`requestWasSuccessful`] | `function` | Used by `skipSuccessfulRequests` and `skipFailedRequests`. |
| [`validate`] | `boolean` \| `object` | Enable or disable built-in validation checks. |
## Thank You
Sponsored by [Zuplo](https://zuplo.link/express-rate-limit) a fully-managed API
Gateway for developers. Add
[dynamic rate-limiting](https://zuplo.link/dynamic-rate-limiting),
authentication and more to any API in minutes. Learn more at
[zuplo.com](https://zuplo.link/express-rate-limit)
<p align="center">
<a href="https://zuplo.link/express-rate-limit">
<picture width="322">
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/express-rate-limit/express-rate-limit/assets/114976/cd2f6fa7-eae1-4fbb-be7d-b17df4c6f383">
<img alt="zuplo-logo" src="https://github.com/express-rate-limit/express-rate-limit/assets/114976/66fd75fa-b39e-4a8c-8d7a-52369bf244dc" width="322">
</picture>
</a>
</p>
---
Thanks to Mintlify for hosting the documentation at
[express-rate-limit.mintlify.app](https://express-rate-limit.mintlify.app)
<p align="center">
<a href="https://mintlify.com/?utm_campaign=devmark&utm_medium=readme&utm_source=express-rate-limit">
<img height="75" src="https://devmark-public-assets.s3.us-west-2.amazonaws.com/sponsorships/mintlify.svg" alt="Create your docs today">
</a>
</p>
---
Finally, thank you to everyone who's contributed to this project in any way! 🫶
## Issues and Contributing
If you encounter a bug or want to see something added/changed, please go ahead
and
[open an issue](https://github.com/express-rate-limit/express-rate-limit/issues/new)!
If you need help with something, feel free to
[start a discussion](https://github.com/express-rate-limit/express-rate-limit/discussions/new)!
If you wish to contribute to the library, thanks! First, please read
[the contributing guide](https://express-rate-limit.mintlify.app/docs/guides/contributing.mdx).
Then you can pick up any issue and fix/implement it!
## License
MIT © [Nathan Friedly](http://nfriedly.com/),
[Vedant K](https://github.com/gamemaker1)
[`windowMs`]:
https://express-rate-limit.mintlify.app/reference/configuration#windowms
[`limit`]: https://express-rate-limit.mintlify.app/reference/configuration#limit
[`message`]:
https://express-rate-limit.mintlify.app/reference/configuration#message
[`statusCode`]:
https://express-rate-limit.mintlify.app/reference/configuration#statuscode
[`handler`]:
https://express-rate-limit.mintlify.app/reference/configuration#handler
[`legacyHeaders`]:
https://express-rate-limit.mintlify.app/reference/configuration#legacyheaders
[`standardHeaders`]:
https://express-rate-limit.mintlify.app/reference/configuration#standardheaders
[`identifier`]:
https://express-rate-limit.mintlify.app/reference/configuration#identifier
[`store`]: https://express-rate-limit.mintlify.app/reference/configuration#store
[`passOnStoreError`]:
https://express-rate-limit.mintlify.app/reference/configuration#passOnStoreError
[`keyGenerator`]:
https://express-rate-limit.mintlify.app/reference/configuration#keygenerator
[`requestPropertyName`]:
https://express-rate-limit.mintlify.app/reference/configuration#requestpropertyname
[`skip`]: https://express-rate-limit.mintlify.app/reference/configuration#skip
[`skipSuccessfulRequests`]:
https://express-rate-limit.mintlify.app/reference/configuration#skipsuccessfulrequests
[`skipFailedRequests`]:
https://express-rate-limit.mintlify.app/reference/configuration#skipfailedrequests
[`requestWasSuccessful`]:
https://express-rate-limit.mintlify.app/reference/configuration#requestwassuccessful
[`validate`]:
https://express-rate-limit.mintlify.app/reference/configuration#validate

8
node_modules/express-rate-limit/tsconfig.json generated vendored Normal file
View File

@ -0,0 +1,8 @@
{
"include": ["source/"],
"exclude": ["node_modules/"],
"extends": "@express-rate-limit/tsconfig",
"compilerOptions": {
"target": "ES2020"
}
}

16
package-lock.json generated
View File

@ -13,6 +13,7 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.5.0", "dotenv": "^16.5.0",
"express": "^5.1.0", "express": "^5.1.0",
"express-rate-limit": "^7.5.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.2", "multer": "^1.4.5-lts.2",
"pg": "^8.15.1" "pg": "^8.15.1"
@ -230,7 +231,6 @@
"version": "16.5.0", "version": "16.5.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
"integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
"license": "BSD-2-Clause",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -363,6 +363,20 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/express-rate-limit": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
"integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/express-rate-limit"
},
"peerDependencies": {
"express": "^4.11 || 5 || ^5.0.0-beta.1"
}
},
"node_modules/finalhandler": { "node_modules/finalhandler": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",

View File

@ -14,6 +14,7 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.5.0", "dotenv": "^16.5.0",
"express": "^5.1.0", "express": "^5.1.0",
"express-rate-limit": "^7.5.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.2", "multer": "^1.4.5-lts.2",
"pg": "^8.15.1" "pg": "^8.15.1"

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB