diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..87fa3a3
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,45 @@
+NODE_ENV=production
+APP_PORT=3000
+PORT=3000
+TZ=Asia/Ho_Chi_Minh
+APP_TIME_ZONE=Asia/Ho_Chi_Minh
+
+# Database: never commit real credentials.
+DB_SERVER=sql-server.internal
+DB_USER=accmanager_app
+DB_PASSWORD=replace-with-a-strong-database-password
+DB_NAME=AccManager
+DB_ENCRYPT=true
+DB_TRUST_CERTIFICATE=false
+DB_CONNECT_TIMEOUT=30000
+
+# Generate a separate random value of at least 32 bytes and keep it stable.
+# Changing this value without a key-rotation migration will make stored
+# application credentials unreadable.
+DATA_ENCRYPTION_SECRET=replace-with-at-least-32-random-bytes
+BCRYPT_ROUNDS=12
+
+# Public HTTPS origin used by email links, CORS and secure cookies.
+APP_BASE_URL=https://accmanager.example.internal
+CORS_ALLOWED_ORIGINS=https://accmanager.example.internal
+COOKIE_SECURE=true
+TRUST_PROXY_HOPS=1
+SESSION_TTL_HOURS=12
+REMEMBER_SESSION_TTL_DAYS=14
+ALLOW_SELF_REGISTRATION=false
+
+# Only needed once when bootstrapping an empty database. Remove the password
+# from the environment after the first administrator has been created.
+INITIAL_ADMIN_USERNAME=admin
+INITIAL_ADMIN_PASSWORD=
+INITIAL_ADMIN_EMAIL=admin@example.internal
+
+SMTP_HOST=
+SMTP_PORT=587
+SMTP_SECURE=false
+SMTP_REQUIRE_TLS=true
+SMTP_USER=
+SMTP_PASS=
+SMTP_FROM=
+EMAIL_VERIFY_TOKEN_TTL_MINUTES=30
+PASSWORD_RESET_TOKEN_TTL_MINUTES=30
diff --git a/.gitignore b/.gitignore
index bfecc27..3db1449 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,8 @@
node_modules
.env
+.env.*
+!.env.example
+DEPLOYMENT_GUIDE.md
docs-output
scripts
.codex-server.err.log
diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md
deleted file mode 100644
index 6f57b8b..0000000
--- a/DEPLOYMENT_GUIDE.md
+++ /dev/null
@@ -1,296 +0,0 @@
-# 📘 Hướng dẫn Triển khai AccManager
-
-Tài liệu này hướng dẫn chi tiết cách build Docker image, push lên registry, và triển khai trên server.
-
----
-
-## 📋 Mục lục
-
-1. [Chuẩn bị](#chuẩn-bị)
-2. [Máy DEV: Build & Push Image](#máy-dev-build--push-image)
-3. [Máy Server: Pull & Deploy](#máy-server-pull--deploy)
-4. [Kiểm tra & Troubleshoot](#kiểm-tra--troubleshoot)
-5. [Cập nhật bản mới](#cập-nhật-bản-mới)
-6. [Public Domain qua Nginx Proxy Manager](#public-domain-qua-nginx-proxy-manager)
-
----
-
-## 🔧 Chuẩn bị
-
-### Tài khoản & Biến môi trường
-
-1. **Docker Hub Account**: Tạo account tại https://hub.docker.com
- - Username: `toiiiiday` (dùng username của bạn)
- - Repository: `accmanager`
-
-2. **File .env trên máy dev** - Kiểm tra nội dung:
- ```env
- NODE_ENV=production
- APP_PORT=3000
- DOCKER_IMAGE=toiiiiday/accmanager:1.0.1
- PORT=3000
- DB_SERVER=172.20.235.176
- DB_USER=sa
- DB_PASSWORD=robotics@2022
- DB_NAME=AccManager
- DB_ENCRYPT=false
- DB_TRUST_CERTIFICATE=true
- DB_CONNECT_TIMEOUT=30000
- BCRYPT_ROUNDS=12
- ```
-
-3. **Thư mục trên server** - SSH vào server tạo:
- ```bash
- mkdir -p ~/accmanager
- cd ~/accmanager
- ```
-
----
-
-## 🖥️ Máy DEV: Build & Push Image
-
-### Bước 1: Chọn version mới
-
-Mỗi lần sửa code và muốn deploy, chọn một tag mới theo thứ tự tăng dần.
-
-Ví dụ:
-- Bản cũ đang chạy: `1.0.2`
-- Bản mới sau khi sửa code: `1.0.3`
-
-### Bước 2: Build image mới (trên máy DEV)
-
-```powershell
-cd D:\RoboticsSource\AccManager
-docker build -t toiiiiday/accmanager:"version" .
-```
-
-### Bước 3: Push image mới lên Docker Hub
-
-```powershell
-docker push toiiiiday/accmanager:"version"
-```
-
-### Bước 4: Kiểm tra image đã có trên registry
-
-```powershell
-docker image ls | findstr toiiiiday/accmanager
-```
-
-Hoặc kiểm tra trên Docker Hub:
-https://hub.docker.com/r/toiiiiday/accmanager/tags
-
-### Bước 5: Cập nhật .env
-
-Sửa dòng `DOCKER_IMAGE`:
-```
-DOCKER_IMAGE=toiiiiday/accmanager:"version"
-```
-
-Mẹo PowerShell (cập nhật nhanh):
-```powershell
-(Get-Content .env) -replace '^DOCKER_IMAGE=.*', 'DOCKER_IMAGE=toiiiiday/accmanager:1.0.3' | Set-Content .env
-```
-
-### Bước 6: Copy confi lên server
-
-Từ máy dev:
-```powershell
-scp .env robotics@172.20.235.176:~/accmanager/.env
-scp docker-compose.yml robotics@172.20.235.176:~/accmanager/docker-compose.yml
-scp docker-compose.image.yml robotics@172.20.235.176:~/accmanager/docker-compose.image.yml
-```
-
----
-
-## 🐧 Máy Server: Pull & Deploy
-
-### Bước 1: SSH vào server
-
-```bash
-ssh robotics@172.20.235.176
-```
-
-### Bước 2: Vào thư mục deploy
-
-```bash
-cd ~/accmanager
-```
-
-### Bước 3: Pull image mới và chạy lại container
-
-```bash
-docker compose --env-file .env -f docker-compose.image.yml pull accmanager
-docker compose --env-file .env -f docker-compose.image.yml up -d accmanager
-```
-
-Kiểm tra trạng thái:
-```bash
-docker compose -f docker-compose.image.yml ps
-```
-
-Xem log:
-```bash
-docker compose -f docker-compose.image.yml logs -f accmanager
-```
-
----
-
-## ✅ Kiểm tra & Troubleshoot
-
-### Kiểm tra app chạy OK
-
-```bash
-# Kiểm tra container đang running
-docker compose -f docker-compose.image.yml ps
-
-# Xem log (tìm "Server running")
-docker compose -f docker-compose.image.yml logs --tail=50 accmanager
-
-
-### Nếu gặp lỗi
-
-**Lỗi: "image not found"**
-- Kiểm tra: `cat .env | grep DOCKER_IMAGE`
-- Đảm bảo image đã push lên Docker Hub, kiểm tra: https://hub.docker.com/r/toiiiiday/accmanager
-
-**Lỗi: "connection refused"**
-- Kiểm tra DB Server có chạy: `ssh robotics@172.20.235.176`
-- Kiểm tra DB credentials trong .env
-
-**Lỗi: Container restart liên tục**
-- Xem log: `docker compose -f docker-compose.image.yml logs --tail=100 accmanager`
-
----
-
-## 🔄 Cập nhật bản mới
-
-Áp dụng đúng 8 bước sau cho mỗi lần sửa code:
-
-### Trên máy DEV
-
-1. Chọn version mới (ví dụ `1.0.3`)
-2. Build:
-```powershell
-docker build -t toiiiiday/accmanager:"version" .
-```
-3. Push:
-```powershell
-docker push toiiiiday/accmanager:"version"
-```
-4. Cập nhật `.env`:
-```env
-DOCKER_IMAGE=toiiiiday/accmanager:"version"
-```
-5. Copy `.env` lên server:
-```powershell
-scp .env robotics@172.20.235.176:~/accmanager/.env
-```
-
-### Trên máy SERVER
-
-6. SSH và vào thư mục deploy:
-```bash
-ssh robotics@172.20.235.176
-cd ~/accmanager
-```
-7. Pull + Up:
-```bash
-docker compose --env-file .env -f docker-compose.image.yml pull accmanager
-docker compose --env-file .env -f docker-compose.image.yml up -d accmanager
-```
-8. Kiểm tra bản mới đã chạy:
-```bash
-docker compose -f docker-compose.image.yml ps
-docker compose -f docker-compose.image.yml logs --tail=50 accmanager
-```
-
-### Có cần gắn tag/version cho mỗi phiên bản mới không?
-
-**Có, nên làm bắt buộc cho production.**
-
-Lý do:
-1. Tránh đè image cũ và tránh nhầm lẫn khi deploy.
-2. Rollback nhanh về bản ổn định trước đó.
-3. Truy vết được bản code nào đang chạy trên server.
-4. Tránh rủi ro do dùng `latest` (khó kiểm soát).
-
-Quy ước khuyến nghị:
-- `1.0.2` -> fix nhỏ
-- `1.1.0` -> thêm tính năng
-- `2.0.0` -> thay đổi lớn/breaking
-
-Ví dụ rollback về bản cũ `1.0.2`:
-```env
-DOCKER_IMAGE=toiiiiday/accmanager:1.0.2
-```
-```bash
-docker compose --env-file .env -f docker-compose.image.yml pull accmanager
-docker compose --env-file .env -f docker-compose.image.yml up -d accmanager
-```
-
----
-
-## 🌐 Public Domain qua Nginx Proxy Manager
-
-### Chuẩn bị
-
-1. **Domain**
- - Trỏ DNS A record về IP public nơi đặt Nginx Proxy Manager
-
-2. **Firewall/Router**
- - Mở inbound port 80, 443 từ Internet
- - Port 3000 chỉ nội bộ (không public)
-
-
-
----
-
-## 📝 Các file liên quan
-
-- `docker-compose.yml` - Build local
-- `docker-compose.image.yml` - Pull & run từ registry
-- `.env` - Biến môi trường
-- `.dockerignore` - Ignore file khi build
-- `Dockerfile` - Config image
-- `deploy-dev.ps1` - Script build & push (Windows)
-- `deploy-server.sh` - Script pull & deploy (Linux)
-
----
-
-## 🎯 Tóm tắt quy trình
-
-```
-Máy DEV
-├─ Chỉnh sửa code
-├─ docker build -t toiiiiday/accmanager:X .
-├─ docker push toiiiiday/accmanager:X
-├─ sửa DOCKER_IMAGE trong .env
-└─ scp .env server (copy env)
-
-Máy Server
-├─ ssh vào server
-├─ cd ~/accmanager
-├─ docker compose pull accmanager
-└─ docker compose up -d accmanager
-
-Nginx Proxy Manager
-└─ Forward từ domain → http://172.20.235.176:3000
-```
-
----
-
-## 💡 Mẹo
-
-1. **Luôn tăng version tag**: 1.0.1 → 1.0.2 → 1.0.3
-2. **Rollback nhanh**: Chỉ cần đổi DOCKER_IMAGE trong .env sang tag cũ rồi deploy lại
-3. **Giữ log**: `docker compose logs --tail=1000 > backup.log`
-4. **Restart container**: `docker compose -f docker-compose.image.yml restart accmanager`
-5. **Xóa container cũ**: `docker compose -f docker-compose.image.yml down`
-6. **Không dùng `latest` cho production**: luôn deploy bằng tag cụ thể
-
----
-
-**Cần giúp? Xem log chi tiết:**
-```bash
-docker compose -f docker-compose.image.yml logs --tail=200 accmanager
-```
diff --git a/Dockerfile b/Dockerfile
index 9ce880d..a4cf8c5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM node:20-bookworm-slim
+FROM node:22-bookworm-slim
WORKDIR /app
diff --git a/backend/server.js b/backend/server.js
index b26929c..a51848b 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -10,6 +10,8 @@ const nodemailer = require('nodemailer');
const multer = require('multer');
const XLSX = require('xlsx');
const dotenv = require('dotenv');
+const helmet = require('helmet');
+const { rateLimit } = require('express-rate-limit');
dotenv.config();
@@ -17,6 +19,7 @@ const APP_TIME_ZONE = process.env.APP_TIME_ZONE || process.env.TZ || 'Asia/Ho_Ch
process.env.TZ = APP_TIME_ZONE;
const app = express();
+const IS_PRODUCTION = process.env.NODE_ENV === 'production';
function envBool(name, defaultValue) {
const value = process.env[name];
@@ -27,23 +30,36 @@ function envBool(name, defaultValue) {
return ['1', 'true', 'yes', 'on'].includes(String(value).toLowerCase());
}
-const DB_SERVER = process.env.DB_SERVER || '172.20.235.176';
+const DB_SERVER = process.env.DB_SERVER || 'localhost';
const DB_USER = process.env.DB_USER || 'sa';
-const DB_PASSWORD = process.env.DB_PASSWORD || 'robotics@2022';
+const DB_PASSWORD = process.env.DB_PASSWORD || '';
const DB_NAME = process.env.DB_NAME || 'AccManager';
-const DB_ENCRYPT = envBool('DB_ENCRYPT', false);
-const DB_TRUST_CERTIFICATE = envBool('DB_TRUST_CERTIFICATE', true);
+const DB_ENCRYPT = envBool('DB_ENCRYPT', IS_PRODUCTION);
+const DB_TRUST_CERTIFICATE = envBool('DB_TRUST_CERTIFICATE', !IS_PRODUCTION);
const DB_CONNECT_TIMEOUT = Number(process.env.DB_CONNECT_TIMEOUT || 30000);
-const BCRYPT_ROUNDS = Number(process.env.BCRYPT_ROUNDS || 12);
-const PASSWORD_VIEW_SECRET = process.env.PASSWORD_VIEW_SECRET || 'change-this-password-view-secret';
-const PASSWORD_VIEW_KEY = crypto.createHash('sha256').update(String(PASSWORD_VIEW_SECRET)).digest();
-const PASSWORD_VIEW_PREFIX = 'enc:v1';
+const configuredBcryptRounds = Number(process.env.BCRYPT_ROUNDS || 12);
+const BCRYPT_ROUNDS = Number.isInteger(configuredBcryptRounds)
+ ? Math.min(15, Math.max(10, configuredBcryptRounds))
+ : 12;
+const DATA_ENCRYPTION_SECRET = String(
+ process.env.DATA_ENCRYPTION_SECRET
+ || process.env.PASSWORD_VIEW_SECRET
+ || ''
+);
+const DATA_ENCRYPTION_KEY = crypto.createHash('sha256').update(DATA_ENCRYPTION_SECRET).digest();
+const DATA_ENCRYPTION_PREFIX = 'enc:v2';
const PORT = process.env.PORT || 3000;
const APP_BASE_URL = String(process.env.APP_BASE_URL || `http://localhost:${PORT}`).replace(/\/+$/, '');
+const SESSION_COOKIE_NAME = 'accmanager_session';
+const SESSION_TTL_HOURS = Math.min(24, Math.max(1, Number(process.env.SESSION_TTL_HOURS || 12)));
+const REMEMBER_SESSION_TTL_DAYS = Math.min(30, Math.max(1, Number(process.env.REMEMBER_SESSION_TTL_DAYS || 14)));
+const COOKIE_SECURE = envBool('COOKIE_SECURE', IS_PRODUCTION);
+const ALLOW_SELF_REGISTRATION = envBool('ALLOW_SELF_REGISTRATION', false);
const SMTP_HOST = process.env.SMTP_HOST || '';
const SMTP_PORT = Number(process.env.SMTP_PORT || 587);
const SMTP_SECURE = envBool('SMTP_SECURE', false);
+const SMTP_REQUIRE_TLS = envBool('SMTP_REQUIRE_TLS', IS_PRODUCTION && !SMTP_SECURE);
const SMTP_USER = process.env.SMTP_USER || '';
const SMTP_PASS = process.env.SMTP_PASS || '';
const SMTP_FROM = process.env.SMTP_FROM || SMTP_USER || 'no-reply@accmanager.local';
@@ -52,6 +68,26 @@ const PASSWORD_RESET_TOKEN_TTL_MINUTES = Number(process.env.PASSWORD_RESET_TOKEN
let mailTransporter;
+if (IS_PRODUCTION && (!process.env.DB_SERVER || !process.env.DB_USER || !DB_PASSWORD)) {
+ throw new Error('DB_SERVER, DB_USER and DB_PASSWORD are required in production');
+}
+
+if (DATA_ENCRYPTION_SECRET.length < 32) {
+ throw new Error('DATA_ENCRYPTION_SECRET must be configured with at least 32 characters before startup');
+}
+
+if (IS_PRODUCTION && !APP_BASE_URL.startsWith('https://')) {
+ throw new Error('APP_BASE_URL must use HTTPS in production');
+}
+
+if (IS_PRODUCTION && !COOKIE_SECURE) {
+ throw new Error('COOKIE_SECURE must be true in production');
+}
+
+if (IS_PRODUCTION && (!DB_ENCRYPT || DB_TRUST_CERTIFICATE)) {
+ console.warn('[SECURITY] SQL TLS certificate validation is not fully enabled. Set DB_ENCRYPT=true and DB_TRUST_CERTIFICATE=false when the SQL Server has a trusted certificate.');
+}
+
const appTimePartsFormatter = new Intl.DateTimeFormat('en-CA', {
timeZone: APP_TIME_ZONE,
year: 'numeric',
@@ -115,21 +151,25 @@ async function verifyPassword(plainPassword, storedPassword) {
return String(plainPassword) === String(storedPassword || '');
}
-function encryptPasswordForView(plainPassword) {
+function encryptSensitiveValue(plainValue) {
+ if (plainValue === null || plainValue === undefined || plainValue === '') {
+ return '';
+ }
+
const iv = crypto.randomBytes(12);
- const cipher = crypto.createCipheriv('aes-256-gcm', PASSWORD_VIEW_KEY, iv);
+ const cipher = crypto.createCipheriv('aes-256-gcm', DATA_ENCRYPTION_KEY, iv);
const encrypted = Buffer.concat([
- cipher.update(String(plainPassword), 'utf8'),
+ cipher.update(String(plainValue), 'utf8'),
cipher.final()
]);
const tag = cipher.getAuthTag();
- return `${PASSWORD_VIEW_PREFIX}:${iv.toString('base64')}:${tag.toString('base64')}:${encrypted.toString('base64')}`;
+ return `${DATA_ENCRYPTION_PREFIX}:${iv.toString('base64')}:${tag.toString('base64')}:${encrypted.toString('base64')}`;
}
-function decryptPasswordForView(payload) {
+function decryptSensitiveValue(payload) {
try {
- if (typeof payload !== 'string' || !payload.startsWith(`${PASSWORD_VIEW_PREFIX}:`)) {
+ if (typeof payload !== 'string' || !payload.startsWith(`${DATA_ENCRYPTION_PREFIX}:`)) {
return null;
}
@@ -141,7 +181,17 @@ function decryptPasswordForView(payload) {
const iv = Buffer.from(parts[2], 'base64');
const tag = Buffer.from(parts[3], 'base64');
const encrypted = Buffer.from(parts[4], 'base64');
- const decipher = crypto.createDecipheriv('aes-256-gcm', PASSWORD_VIEW_KEY, iv);
+ if (
+ iv.length !== 12
+ || tag.length !== 16
+ || encrypted.length === 0
+ || iv.toString('base64') !== parts[2]
+ || tag.toString('base64') !== parts[3]
+ || encrypted.toString('base64') !== parts[4]
+ ) {
+ return null;
+ }
+ const decipher = crypto.createDecipheriv('aes-256-gcm', DATA_ENCRYPTION_KEY, iv);
decipher.setAuthTag(tag);
const plain = Buffer.concat([decipher.update(encrypted), decipher.final()]);
return plain.toString('utf8');
@@ -180,6 +230,9 @@ function getMailTransporter() {
host: SMTP_HOST,
port: SMTP_PORT,
secure: SMTP_SECURE,
+ requireTLS: SMTP_REQUIRE_TLS,
+ disableFileAccess: true,
+ disableUrlAccess: true,
auth: {
user: SMTP_USER,
pass: SMTP_PASS
@@ -192,12 +245,14 @@ function getMailTransporter() {
async function sendVerificationEmail({ email, username, token }) {
const verifyUrl = getEmailVerificationUrl(token);
+ const safeUsername = escapeHtml(username || 'there');
+ const safeVerifyUrl = escapeHtml(verifyUrl);
if (!canSendEmails()) {
- console.warn(`SMTP is not configured. Verification URL for ${email}: ${verifyUrl}`);
+ console.warn(`SMTP is not configured. Cannot send verification email to ${email}.`);
return {
sent: false,
- previewUrl: verifyUrl,
+ ...(!IS_PRODUCTION ? { previewUrl: verifyUrl } : {}),
reason: 'SMTP is not configured'
};
}
@@ -212,13 +267,13 @@ async function sendVerificationEmail({ email, username, token }) {
html: `
Confirm your email
-
Hello ${username || 'there'},
+
Hello ${safeUsername},
Thank you for registering. Please confirm your email by clicking the button below:
- Confirm Email
+ Confirm Email
Or copy this URL into your browser:
-
${verifyUrl}
+
${safeVerifyUrl}
This link will expire in ${EMAIL_VERIFY_TOKEN_TTL_MINUTES} minutes.
If you did not register this account, you can ignore this message.
@@ -237,12 +292,14 @@ async function sendVerificationEmail({ email, username, token }) {
async function sendPasswordResetEmail({ email, username, token }) {
const resetUrl = getPasswordResetUrl(token);
+ const safeUsername = escapeHtml(username || 'there');
+ const safeResetUrl = escapeHtml(resetUrl);
if (!canSendEmails()) {
- console.warn(`SMTP is not configured. Password reset URL for ${email}: ${resetUrl}`);
+ console.warn(`SMTP is not configured. Cannot send password reset email to ${email}.`);
return {
sent: false,
- previewUrl: resetUrl,
+ ...(!IS_PRODUCTION ? { previewUrl: resetUrl } : {}),
reason: 'SMTP is not configured'
};
}
@@ -257,13 +314,13 @@ async function sendPasswordResetEmail({ email, username, token }) {
html: `
Reset your password
-
Hello ${username || 'there'},
+
Hello ${safeUsername},
We received a password reset request for your account.
- Reset Password
+ Reset Password
Or copy this URL into your browser:
-
${resetUrl}
+
${safeResetUrl}
This link will expire in ${PASSWORD_RESET_TOKEN_TTL_MINUTES} minutes.
If you did not request this reset, you can ignore this message.
@@ -281,11 +338,33 @@ async function sendPasswordResetEmail({ email, username, token }) {
}
function getUserIdFromRequest(req) {
- const rawUserId = req.headers['x-user-id'] || req.query.userId;
- const userId = Number(rawUserId);
+ const userId = Number(req.user?.UserId);
return Number.isInteger(userId) && userId > 0 ? userId : null;
}
+function getRequesterRole(req) {
+ return normalizeRole(req.user?.Role || req.user?.RoleName);
+}
+
+function sendInternalError(res, err, publicMessage = 'Internal server error') {
+ console.error('Request failed:', err?.message || err);
+ return res.status(500).json({ success: false, message: publicMessage });
+}
+
+function normalizeOptionalHttpUrl(value) {
+ const text = String(value || '').trim();
+ if (!text) {
+ return '';
+ }
+
+ try {
+ const parsed = new URL(text);
+ return ['http:', 'https:'].includes(parsed.protocol) ? parsed.toString() : null;
+ } catch (err) {
+ return null;
+ }
+}
+
async function getUserDisplayNameById(userId) {
if (!userId) {
return null;
@@ -742,6 +821,19 @@ function normalizeImportToken(value) {
.replace(/[^a-z0-9]/g, '');
}
+function isEncryptedSensitiveValue(value) {
+ return typeof value === 'string' && value.startsWith(`${DATA_ENCRYPTION_PREFIX}:`);
+}
+
+function escapeHtml(value) {
+ return String(value || '')
+ .replaceAll('&', '&')
+ .replaceAll('<', '<')
+ .replaceAll('>', '>')
+ .replaceAll('"', '"')
+ .replaceAll("'", ''');
+}
+
function normalizeAssetDamageType(value) {
const normalized = normalizeImportToken(value);
if (['disposed', 'dispose', 'disposal', 'thanhly', 'liquidated', 'liquidation'].includes(normalized)) {
@@ -2019,12 +2111,114 @@ function parseConsumableImportRowsFromWorkbook(workbook) {
};
}
-// Middleware
-app.use(cors());
-app.use(express.json());
+// Security and request middleware
+app.disable('x-powered-by');
+app.set('trust proxy', Number(process.env.TRUST_PROXY_HOPS || (IS_PRODUCTION ? 1 : 0)));
+app.use(helmet({
+ contentSecurityPolicy: {
+ directives: {
+ defaultSrc: ["'self'"],
+ baseUri: ["'self'"],
+ connectSrc: ["'self'"],
+ fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com'],
+ formAction: ["'self'"],
+ frameAncestors: ["'none'"],
+ imgSrc: ["'self'", 'data:', 'blob:'],
+ objectSrc: ["'none'"],
+ scriptSrc: ["'self'", "'unsafe-inline'", 'https://cdn.jsdelivr.net', 'https://cdn.sheetjs.com'],
+ styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com', 'https://cdn.jsdelivr.net'],
+ workerSrc: ["'self'", 'blob:'],
+ ...(IS_PRODUCTION ? { upgradeInsecureRequests: [] } : {})
+ }
+ },
+ crossOriginEmbedderPolicy: false,
+ strictTransportSecurity: IS_PRODUCTION
+ ? { maxAge: 31536000, includeSubDomains: true }
+ : false
+}));
+
+const configuredOrigins = String(process.env.CORS_ALLOWED_ORIGINS || '')
+ .split(',')
+ .map(origin => origin.trim().replace(/\/+$/, ''))
+ .filter(Boolean);
+const allowedOrigins = new Set(configuredOrigins);
+try {
+ allowedOrigins.add(new URL(APP_BASE_URL).origin);
+} catch (err) {
+ console.warn('[SECURITY] APP_BASE_URL is not a valid absolute URL; configure CORS_ALLOWED_ORIGINS explicitly.');
+}
+
+function requestOriginIsAllowed(req) {
+ const origin = String(req.headers.origin || '').replace(/\/+$/, '');
+ if (!origin) {
+ return true;
+ }
+
+ const requestOrigin = `${req.protocol}://${req.get('host')}`.replace(/\/+$/, '');
+ return origin === requestOrigin || allowedOrigins.has(origin);
+}
+
+app.use(cors({
+ origin(origin, callback) {
+ if (!origin || allowedOrigins.has(String(origin).replace(/\/+$/, ''))) {
+ return callback(null, true);
+ }
+ return callback(null, false);
+ },
+ credentials: true,
+ methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
+ allowedHeaders: ['Content-Type', 'X-Requested-With']
+}));
+app.use((req, res, next) => {
+ if (!['GET', 'HEAD', 'OPTIONS'].includes(req.method) && !requestOriginIsAllowed(req)) {
+ return res.status(403).json({ success: false, message: 'Request origin is not allowed' });
+ }
+ next();
+});
+app.use(express.json({ limit: '1mb', strict: true }));
+app.use('/api', (req, res, next) => {
+ res.setHeader('Cache-Control', 'no-store');
+ next();
+});
+
+const loginRateLimit = rateLimit({
+ windowMs: 15 * 60 * 1000,
+ limit: 10,
+ standardHeaders: 'draft-8',
+ legacyHeaders: false,
+ message: { success: false, message: 'Too many sign-in attempts. Please try again later.' }
+});
+const accountRecoveryRateLimit = rateLimit({
+ windowMs: 60 * 60 * 1000,
+ limit: 5,
+ standardHeaders: 'draft-8',
+ legacyHeaders: false,
+ message: { success: false, message: 'Too many requests. Please try again later.' }
+});
+const credentialRevealRateLimit = rateLimit({
+ windowMs: 15 * 60 * 1000,
+ limit: 60,
+ standardHeaders: 'draft-8',
+ legacyHeaders: false,
+ message: { success: false, message: 'Too many credential reveal requests. Please try again later.' }
+});
+
+const allowedSpreadsheetExtensions = new Set(['.xls', '.xlsx']);
const upload = multer({
storage: multer.memoryStorage(),
- limits: { fileSize: 15 * 1024 * 1024 }
+ limits: {
+ fileSize: 10 * 1024 * 1024,
+ files: 1,
+ fields: 10,
+ parts: 12
+ },
+ fileFilter(req, file, callback) {
+ const extension = require('path').extname(String(file.originalname || '')).toLowerCase();
+ if (!allowedSpreadsheetExtensions.has(extension)) {
+ return callback(new Error('Only .xls and .xlsx files are allowed'));
+ }
+ callback(null, true);
+ }
});
// Serve static files from /public
@@ -2060,6 +2254,164 @@ const sqlConfig = {
// Initialize Database Pool
let pool;
+function parseRequestCookies(req) {
+ const cookies = {};
+ const header = String(req.headers.cookie || '');
+ for (const part of header.split(';')) {
+ const separatorIndex = part.indexOf('=');
+ if (separatorIndex <= 0) {
+ continue;
+ }
+
+ const name = part.slice(0, separatorIndex).trim();
+ const rawValue = part.slice(separatorIndex + 1).trim();
+ try {
+ cookies[name] = decodeURIComponent(rawValue);
+ } catch (err) {
+ cookies[name] = rawValue;
+ }
+ }
+ return cookies;
+}
+
+function hashSessionToken(token) {
+ return crypto.createHash('sha256').update(String(token)).digest('hex');
+}
+
+function getSessionLifetimeMs(remember) {
+ return remember
+ ? REMEMBER_SESSION_TTL_DAYS * 24 * 60 * 60 * 1000
+ : SESSION_TTL_HOURS * 60 * 60 * 1000;
+}
+
+function setSessionCookie(res, token, expiresAt) {
+ res.cookie(SESSION_COOKIE_NAME, token, {
+ httpOnly: true,
+ secure: COOKIE_SECURE,
+ sameSite: 'strict',
+ path: '/',
+ expires: expiresAt
+ });
+}
+
+function clearSessionCookie(res) {
+ res.clearCookie(SESSION_COOKIE_NAME, {
+ httpOnly: true,
+ secure: COOKIE_SECURE,
+ sameSite: 'strict',
+ path: '/'
+ });
+}
+
+function toSafeUser(user) {
+ if (!user) {
+ return null;
+ }
+
+ const role = user.Role || user.RoleName || 'guest';
+ return {
+ UserId: user.UserId,
+ Username: user.Username,
+ Email: user.Email,
+ FullName: user.FullName,
+ Role: role,
+ role,
+ RoleId: user.RoleId,
+ Status: user.Status,
+ EmailVerified: Boolean(user.EmailVerified)
+ };
+}
+
+async function createAuthSession(userId, req, res, remember = false) {
+ const token = crypto.randomBytes(32).toString('base64url');
+ const tokenHash = hashSessionToken(token);
+ const expiresAt = new Date(Date.now() + getSessionLifetimeMs(remember));
+ const userAgent = String(req.get('user-agent') || '').slice(0, 500);
+ const ipAddress = String(req.ip || req.socket?.remoteAddress || '').slice(0, 64);
+
+ await pool.request()
+ .input('tokenHash', sql.Char(64), tokenHash)
+ .input('userId', sql.Int, userId)
+ .input('expiresAt', sql.DateTime2, expiresAt)
+ .input('userAgent', sql.NVarChar(500), userAgent || null)
+ .input('ipAddress', sql.NVarChar(64), ipAddress || null)
+ .query(`
+ DELETE FROM AuthSessions WHERE ExpiresAt <= SYSUTCDATETIME();
+ INSERT INTO AuthSessions (TokenHash, UserId, ExpiresAt, UserAgent, IpAddress)
+ VALUES (@tokenHash, @userId, @expiresAt, @userAgent, @ipAddress);
+ `);
+
+ setSessionCookie(res, token, expiresAt);
+}
+
+async function destroyAuthSession(req, res) {
+ const token = parseRequestCookies(req)[SESSION_COOKIE_NAME];
+ if (token) {
+ await pool.request()
+ .input('tokenHash', sql.Char(64), hashSessionToken(token))
+ .query('DELETE FROM AuthSessions WHERE TokenHash = @tokenHash');
+ }
+ clearSessionCookie(res);
+}
+
+async function invalidateUserSessions(userId, exceptSessionId = null) {
+ if (!userId) {
+ return;
+ }
+
+ const request = pool.request().input('userId', sql.Int, userId);
+ if (exceptSessionId) {
+ request.input('sessionId', sql.BigInt, exceptSessionId);
+ await request.query('DELETE FROM AuthSessions WHERE UserId = @userId AND SessionId <> @sessionId');
+ return;
+ }
+ await request.query('DELETE FROM AuthSessions WHERE UserId = @userId');
+}
+
+async function requireAuth(req, res, next) {
+ try {
+ const token = parseRequestCookies(req)[SESSION_COOKIE_NAME];
+ if (!token) {
+ return res.status(401).json({ success: false, message: 'Authentication required' });
+ }
+
+ const result = await pool.request()
+ .input('tokenHash', sql.Char(64), hashSessionToken(token))
+ .query(`
+ SELECT TOP 1
+ s.SessionId,
+ u.UserId,
+ u.Username,
+ u.Email,
+ u.FullName,
+ COALESCE(r.RoleName, u.Role) AS Role,
+ u.RoleId,
+ u.Status,
+ u.EmailVerified
+ FROM AuthSessions s
+ INNER JOIN Users u ON u.UserId = s.UserId
+ LEFT JOIN Roles r ON r.RoleId = u.RoleId
+ WHERE s.TokenHash = @tokenHash
+ AND s.ExpiresAt > SYSUTCDATETIME()
+ AND u.IsActive = 1
+ AND u.EmailVerified = 1;
+ `);
+
+ if (result.recordset.length === 0) {
+ clearSessionCookie(res);
+ return res.status(401).json({ success: false, message: 'Session is invalid or expired' });
+ }
+
+ const authenticated = result.recordset[0];
+ req.authSessionId = authenticated.SessionId;
+ req.user = toSafeUser(authenticated);
+ next();
+ } catch (err) {
+ console.error('Authentication middleware error:', err.message);
+ res.status(500).json({ success: false, message: 'Unable to validate session' });
+ }
+}
+
async function initializeDatabase() {
try {
pool = new sql.ConnectionPool(sqlConfig);
@@ -2090,6 +2442,7 @@ async function initializeDatabase() {
// Now create tables in AccManager
await createTables();
await migrateLegacyPasswords();
+ await migrateStoredAccountPasswords();
console.log('[OK] Database and tables created');
} catch (err) {
@@ -2101,36 +2454,24 @@ async function initializeDatabase() {
async function migrateLegacyPasswords() {
try {
const usersResult = await pool.request()
- .query('SELECT UserId, Password, ViewPassword FROM Users WHERE Password IS NOT NULL');
+ .query('SELECT UserId, Password FROM Users WHERE Password IS NOT NULL');
let migratedCount = 0;
for (const row of usersResult.recordset) {
- const request = pool.request()
- .input('userId', sql.Int, row.UserId);
-
- let hasUpdates = false;
const rawPassword = String(row.Password || '');
-
- if (!row.ViewPassword && !isBcryptHash(rawPassword)) {
- request.input('viewPassword', sql.NVarChar, encryptPasswordForView(rawPassword));
- hasUpdates = true;
- }
-
- if (!isBcryptHash(row.Password)) {
- const hashedPassword = await hashPassword(row.Password);
- request.input('password', sql.NVarChar, hashedPassword);
- hasUpdates = true;
+ if (!isBcryptHash(rawPassword)) {
+ await pool.request()
+ .input('userId', sql.Int, row.UserId)
+ .input('password', sql.NVarChar, await hashPassword(rawPassword))
+ .query('UPDATE Users SET Password = @password WHERE UserId = @userId');
migratedCount += 1;
}
-
- if (hasUpdates) {
- await request.query(`UPDATE Users
- SET ${!isBcryptHash(rawPassword) ? 'Password = @password' : 'Password = Password'}
- ${(!row.ViewPassword && !isBcryptHash(rawPassword)) ? ', ViewPassword = @viewPassword' : ''}
- WHERE UserId = @userId`);
- }
}
+ // Login passwords must never be recoverable. Retain the legacy column only
+ // for backward-compatible schema upgrades, but permanently erase its data.
+ await pool.request().query('UPDATE Users SET ViewPassword = NULL WHERE ViewPassword IS NOT NULL');
+
if (migratedCount > 0) {
console.log(`[OK] Migrated ${migratedCount} legacy plain-text password(s) to bcrypt`);
}
@@ -2139,6 +2480,34 @@ async function migrateLegacyPasswords() {
}
}
+async function migrateStoredAccountPasswords() {
+ try {
+ const accountsResult = await pool.request()
+ .query('SELECT AccountId, AccountPassword FROM Accounts WHERE AccountPassword IS NOT NULL AND AccountPassword <> N\'\'');
+
+ let migratedCount = 0;
+ for (const row of accountsResult.recordset) {
+ const currentValue = String(row.AccountPassword || '');
+ if (isEncryptedSensitiveValue(currentValue)) {
+ continue;
+ }
+
+ await pool.request()
+ .input('accountId', sql.Int, row.AccountId)
+ .input('accountPassword', sql.NVarChar(2048), encryptSensitiveValue(currentValue))
+ .query('UPDATE Accounts SET AccountPassword = @accountPassword WHERE AccountId = @accountId');
+ migratedCount += 1;
+ }
+
+ if (migratedCount > 0) {
+ console.log(`[OK] Encrypted ${migratedCount} stored application password(s)`);
+ }
+ } catch (err) {
+ console.error('Stored account password migration error:', err.message);
+ throw err;
+ }
+}
+
async function ensureAppTimeDefaultConstraints() {
await pool.request().query(`
DECLARE @schemaName SYSNAME = N'dbo';
@@ -2253,6 +2622,23 @@ async function createTables() {
IsActive BIT DEFAULT 1
)
END`,
+
+ // Server-side authentication sessions
+ `IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'AuthSessions')
+ BEGIN
+ CREATE TABLE AuthSessions (
+ SessionId BIGINT PRIMARY KEY IDENTITY(1,1),
+ TokenHash CHAR(64) UNIQUE NOT NULL,
+ UserId INT NOT NULL,
+ ExpiresAt DATETIME2 NOT NULL,
+ CreatedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(),
+ UserAgent NVARCHAR(500),
+ IpAddress NVARCHAR(64),
+ FOREIGN KEY (UserId) REFERENCES Users(UserId) ON DELETE CASCADE
+ );
+ CREATE INDEX IX_AuthSessions_UserId ON AuthSessions(UserId);
+ CREATE INDEX IX_AuthSessions_ExpiresAt ON AuthSessions(ExpiresAt);
+ END`,
// Applications Table
`IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Applications')
@@ -2278,7 +2664,7 @@ async function createTables() {
UserId INT NOT NULL,
AppId INT NOT NULL,
AccountUsername NVARCHAR(100),
- AccountPassword NVARCHAR(255),
+ AccountPassword NVARCHAR(2048),
Email NVARCHAR(100),
AccessLevel NVARCHAR(50),
Status NVARCHAR(20) DEFAULT 'Active',
@@ -3003,6 +3389,7 @@ async function createTables() {
await pool.request().query(`IF COL_LENGTH('dbo.Applications','Url') IS NULL ALTER TABLE Applications ADD Url NVARCHAR(255);`);
await pool.request().query(`IF COL_LENGTH('dbo.Applications','Description') IS NULL ALTER TABLE Applications ADD Description NVARCHAR(500);`);
+ await pool.request().query(`IF COL_LENGTH('dbo.Accounts','AccountPassword') IS NOT NULL ALTER TABLE Accounts ALTER COLUMN AccountPassword NVARCHAR(2048) NULL;`);
await pool.request().query(`IF COL_LENGTH('dbo.Users','ViewPassword') IS NULL ALTER TABLE Users ADD ViewPassword NVARCHAR(1024);`);
await pool.request().query(`IF COL_LENGTH('dbo.Users','EmailVerified') IS NULL ALTER TABLE Users ADD EmailVerified BIT NOT NULL CONSTRAINT DF_Users_EmailVerified DEFAULT(0);`);
await pool.request().query(`IF COL_LENGTH('dbo.Users','EmailVerifiedAt') IS NULL ALTER TABLE Users ADD EmailVerifiedAt DATETIME NULL;`);
@@ -3039,26 +3426,41 @@ async function createTables() {
// Insert initial admin user
try {
- const adminPasswordHash = await hashPassword('admin');
- const adminViewPassword = encryptPasswordForView('admin');
- await pool.request()
- .input('username', sql.NVarChar, 'admin')
- .input('password', sql.NVarChar, adminPasswordHash)
- .input('viewPassword', sql.NVarChar, adminViewPassword)
- .input('email', sql.NVarChar, 'admin@accmanager.local')
- .input('fullname', sql.NVarChar, 'Administrator')
- .input('role', sql.NVarChar, 'admin')
- .query(`IF NOT EXISTS (SELECT * FROM Users WHERE Username = @username)
- INSERT INTO Users (Username, Password, ViewPassword, Email, FullName, Role, IsActive, EmailVerified, EmailVerifiedAt)
- VALUES (@username, @password, @viewPassword, @email, @fullname, @role, 1, 1, DATEADD(HOUR, 7, SYSUTCDATETIME()))
- ELSE
- UPDATE Users
- SET EmailVerified = 1,
- EmailVerifiedAt = ISNULL(EmailVerifiedAt, DATEADD(HOUR, 7, SYSUTCDATETIME()))
- WHERE Username = @username`);
- console.log('[OK] Admin user created: admin / admin');
+ const initialAdminPassword = String(process.env.INITIAL_ADMIN_PASSWORD || '');
+ if (initialAdminPassword) {
+ if (initialAdminPassword.length < 12) {
+ throw new Error('INITIAL_ADMIN_PASSWORD must contain at least 12 characters');
+ }
+
+ const result = await pool.request()
+ .input('username', sql.NVarChar, String(process.env.INITIAL_ADMIN_USERNAME || 'admin').trim())
+ .input('password', sql.NVarChar, await hashPassword(initialAdminPassword))
+ .input('email', sql.NVarChar, String(process.env.INITIAL_ADMIN_EMAIL || 'admin@accmanager.local').trim())
+ .input('fullname', sql.NVarChar, String(process.env.INITIAL_ADMIN_FULLNAME || 'Administrator').trim())
+ .input('role', sql.NVarChar, 'admin')
+ .query(`IF NOT EXISTS (SELECT 1 FROM Users WHERE Username = @username)
+ BEGIN
+ INSERT INTO Users (Username, Password, Email, FullName, Role, IsActive, EmailVerified, EmailVerifiedAt)
+ VALUES (@username, @password, @email, @fullname, @role, 1, 1, DATEADD(HOUR, 7, SYSUTCDATETIME()));
+ SELECT CAST(1 AS BIT) AS Created;
+ END
+ ELSE SELECT CAST(0 AS BIT) AS Created;`);
+
+ if (result.recordset?.[0]?.Created) {
+ console.log('[OK] Initial admin user created from environment configuration');
+ }
+ } else {
+ const adminCount = await pool.request()
+ .query("SELECT COUNT(*) AS AdminCount FROM Users WHERE LOWER(ISNULL(Role, '')) = 'admin' AND IsActive = 1");
+ if (Number(adminCount.recordset?.[0]?.AdminCount || 0) === 0) {
+ console.warn('[SECURITY] No active admin exists. Set a strong INITIAL_ADMIN_PASSWORD once to bootstrap the first administrator.');
+ }
+ }
} catch (err) {
console.error('Admin user error:', err.message);
+ if (IS_PRODUCTION) {
+ throw err;
+ }
}
// Insert sample applications
@@ -3083,10 +3485,16 @@ async function createTables() {
// API ROUTES - Authentication
// ==========================================
+app.get('/api/auth/config', (req, res) => {
+ res.json({ success: true, allowSelfRegistration: ALLOW_SELF_REGISTRATION });
+});
+
// Login endpoint
-app.post('/api/auth/login', async (req, res) => {
+app.post('/api/auth/login', loginRateLimit, async (req, res) => {
try {
- const { username, password } = req.body;
+ const username = String(req.body?.username || '').trim();
+ const password = String(req.body?.password || '');
+ const remember = req.body?.remember === true;
if (!username || !password) {
return res.status(400).json({
@@ -3094,6 +3502,10 @@ app.post('/api/auth/login', async (req, res) => {
message: 'Username and password are required'
});
}
+
+ if (username.length > 254 || password.length > 256) {
+ return res.status(400).json({ success: false, message: 'Invalid username or password' });
+ }
const result = await pool.request()
.input('username', sql.NVarChar, username)
@@ -3129,8 +3541,7 @@ app.post('/api/auth/login', async (req, res) => {
await pool.request()
.input('userId', sql.Int, dbUser.UserId)
.input('password', sql.NVarChar, upgradedHash)
- .input('viewPassword', sql.NVarChar, encryptPasswordForView(password))
- .query('UPDATE Users SET Password = @password, ViewPassword = ISNULL(ViewPassword, @viewPassword) WHERE UserId = @userId');
+ .query('UPDATE Users SET Password = @password, ViewPassword = NULL WHERE UserId = @userId');
}
const { Password: _, ...safeUser } = dbUser;
@@ -3140,6 +3551,9 @@ app.post('/api/auth/login', async (req, res) => {
await pool.request()
.input('userId', sql.Int, user.UserId)
.query('UPDATE Users SET LastLogin = DATEADD(HOUR, 7, SYSUTCDATETIME()) WHERE UserId = @userId');
+
+ await destroyAuthSession(req, res);
+ await createAuthSession(user.UserId, req, res, remember);
res.json({
success: true,
@@ -3147,20 +3561,26 @@ app.post('/api/auth/login', async (req, res) => {
user: user
});
} else {
+ // Equalize the expensive bcrypt work for unknown and known accounts.
+ await hashPassword(password);
res.status(401).json({
success: false,
message: 'Invalid username or password'
});
}
} catch (err) {
- console.error('Login error:', err);
- res.status(500).json({ success: false, message: err.message });
+ console.error('Login error:', err.message);
+ res.status(500).json({ success: false, message: 'Unable to sign in right now' });
}
});
// Public registration endpoint
-app.post('/api/auth/register', async (req, res) => {
+app.post('/api/auth/register', accountRecoveryRateLimit, async (req, res) => {
try {
+ if (!ALLOW_SELF_REGISTRATION) {
+ return res.status(403).json({ success: false, message: 'Self-registration is disabled. Please contact an administrator.' });
+ }
+
const { username, password, email, fullname } = req.body;
if (!username || !password || !email) {
@@ -3169,6 +3589,13 @@ app.post('/api/auth/register', async (req, res) => {
const normalizedEmail = String(email).trim().toLowerCase();
const safeUsername = String(username).trim();
+ const safePassword = String(password || '');
+ if (!/^[A-Za-z0-9._-]{3,50}$/.test(safeUsername)) {
+ return res.status(400).json({ success: false, message: 'Username must be 3-50 characters and contain only letters, numbers, dot, underscore or hyphen' });
+ }
+ if (safePassword.length < 12 || safePassword.length > 256) {
+ return res.status(400).json({ success: false, message: 'Password must be between 12 and 256 characters' });
+ }
const isEmailFormatValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalizedEmail);
if (!isEmailFormatValid) {
return res.status(400).json({ success: false, message: 'Email format is invalid' });
@@ -3189,8 +3616,7 @@ app.post('/api/auth/register', async (req, res) => {
});
}
- const hashedPassword = await hashPassword(password);
- const viewPassword = encryptPasswordForView(password);
+ const hashedPassword = await hashPassword(safePassword);
const { token, tokenHash } = generateEmailVerificationToken();
const safeFullname = fullname && fullname.trim() ? fullname.trim() : safeUsername;
@@ -3234,12 +3660,11 @@ app.post('/api/auth/register', async (req, res) => {
.input('fullname', sql.NVarChar, safeFullname)
.input('roleId', sql.Int, guestRoleId)
.input('role', sql.NVarChar, guestRoleName)
- .input('viewPassword', sql.NVarChar, viewPassword)
.input('emailVerifyToken', sql.NVarChar, tokenHash)
.input('tokenTtlMinutes', sql.Int, EMAIL_VERIFY_TOKEN_TTL_MINUTES)
- .query(`INSERT INTO Users (Username, Password, ViewPassword, Email, FullName, RoleId, Role, Status, IsActive, EmailVerified, EmailVerifyToken, EmailVerifyTokenExpires)
+ .query(`INSERT INTO Users (Username, Password, Email, FullName, RoleId, Role, Status, IsActive, EmailVerified, EmailVerifyToken, EmailVerifyTokenExpires)
OUTPUT INSERTED.UserId, INSERTED.Username, INSERTED.Email, INSERTED.FullName, INSERTED.Role, INSERTED.RoleId
- VALUES (@username, @password, @viewPassword, @email, @fullname, @roleId, @role, 'Active', 1, 0, @emailVerifyToken, DATEADD(MINUTE, @tokenTtlMinutes, DATEADD(HOUR, 7, SYSUTCDATETIME())))`);
+ VALUES (@username, @password, @email, @fullname, @roleId, @role, 'Active', 1, 0, @emailVerifyToken, DATEADD(MINUTE, @tokenTtlMinutes, DATEADD(HOUR, 7, SYSUTCDATETIME())))`);
} else {
result = await pool.request()
.input('username', sql.NVarChar, safeUsername)
@@ -3247,12 +3672,11 @@ app.post('/api/auth/register', async (req, res) => {
.input('email', sql.NVarChar, normalizedEmail)
.input('fullname', sql.NVarChar, safeFullname)
.input('role', sql.NVarChar, guestRoleName)
- .input('viewPassword', sql.NVarChar, viewPassword)
.input('emailVerifyToken', sql.NVarChar, tokenHash)
.input('tokenTtlMinutes', sql.Int, EMAIL_VERIFY_TOKEN_TTL_MINUTES)
- .query(`INSERT INTO Users (Username, Password, ViewPassword, Email, FullName, Role, Status, IsActive, EmailVerified, EmailVerifyToken, EmailVerifyTokenExpires)
+ .query(`INSERT INTO Users (Username, Password, Email, FullName, Role, Status, IsActive, EmailVerified, EmailVerifyToken, EmailVerifyTokenExpires)
OUTPUT INSERTED.UserId, INSERTED.Username, INSERTED.Email, INSERTED.FullName, INSERTED.Role
- VALUES (@username, @password, @viewPassword, @email, @fullname, @role, 'Active', 1, 0, @emailVerifyToken, DATEADD(MINUTE, @tokenTtlMinutes, DATEADD(HOUR, 7, SYSUTCDATETIME())))`);
+ VALUES (@username, @password, @email, @fullname, @role, 'Active', 1, 0, @emailVerifyToken, DATEADD(MINUTE, @tokenTtlMinutes, DATEADD(HOUR, 7, SYSUTCDATETIME())))`);
}
const inserted = result.recordset[0];
@@ -3284,11 +3708,11 @@ app.post('/api/auth/register', async (req, res) => {
userId: inserted?.UserId
};
- if (emailResult.previewUrl) {
+ if (!IS_PRODUCTION && emailResult.previewUrl) {
responsePayload.verificationPreviewUrl = emailResult.previewUrl;
}
- if (emailResult.reason && !emailResult.sent) {
+ if (!IS_PRODUCTION && emailResult.reason && !emailResult.sent) {
responsePayload.emailError = emailResult.reason;
}
@@ -3347,6 +3771,9 @@ app.get('/api/auth/verify-email', async (req, res) => {
.input('userId', sql.Int, verifiedUser.UserId)
.query('UPDATE Users SET LastLogin = DATEADD(HOUR, 7, SYSUTCDATETIME()) WHERE UserId = @userId');
+ await destroyAuthSession(req, res);
+ await createAuthSession(verifiedUser.UserId, req, res, false);
+
res.json({
success: true,
message: 'Email confirmed successfully. Logging you in...',
@@ -3359,7 +3786,7 @@ app.get('/api/auth/verify-email', async (req, res) => {
}
});
-app.post('/api/auth/resend-verification', async (req, res) => {
+app.post('/api/auth/resend-verification', accountRecoveryRateLimit, async (req, res) => {
try {
const identifier = String(req.body?.identifier || req.body?.email || '').trim();
if (!identifier) {
@@ -3409,11 +3836,11 @@ app.post('/api/auth/resend-verification', async (req, res) => {
emailSent: emailResult.sent
};
- if (emailResult.previewUrl) {
+ if (!IS_PRODUCTION && emailResult.previewUrl) {
payload.verificationPreviewUrl = emailResult.previewUrl;
}
- if (emailResult.reason && !emailResult.sent) {
+ if (!IS_PRODUCTION && emailResult.reason && !emailResult.sent) {
payload.emailError = emailResult.reason;
}
@@ -3424,7 +3851,7 @@ app.post('/api/auth/resend-verification', async (req, res) => {
}
});
-app.post('/api/auth/forgot-password', async (req, res) => {
+app.post('/api/auth/forgot-password', accountRecoveryRateLimit, async (req, res) => {
try {
await ensurePasswordResetColumns();
@@ -3476,11 +3903,11 @@ app.post('/api/auth/forgot-password', async (req, res) => {
emailSent: emailResult.sent
};
- if (emailResult.previewUrl) {
+ if (!IS_PRODUCTION && emailResult.previewUrl) {
payload.resetPreviewUrl = emailResult.previewUrl;
}
- if (emailResult.reason && !emailResult.sent) {
+ if (!IS_PRODUCTION && emailResult.reason && !emailResult.sent) {
payload.emailError = emailResult.reason;
}
@@ -3491,7 +3918,7 @@ app.post('/api/auth/forgot-password', async (req, res) => {
}
});
-app.post('/api/auth/reset-password', async (req, res) => {
+app.post('/api/auth/reset-password', accountRecoveryRateLimit, async (req, res) => {
try {
await ensurePasswordResetColumns();
@@ -3502,8 +3929,8 @@ app.post('/api/auth/reset-password', async (req, res) => {
return res.status(400).json({ success: false, message: 'Reset token and new password are required' });
}
- if (newPassword.length < 6) {
- return res.status(400).json({ success: false, message: 'New password must be at least 6 characters' });
+ if (newPassword.length < 12 || newPassword.length > 256) {
+ return res.status(400).json({ success: false, message: 'New password must be between 12 and 256 characters' });
}
const tokenHash = hashVerificationToken(token);
@@ -3528,19 +3955,18 @@ app.post('/api/auth/reset-password', async (req, res) => {
}
const hashedPassword = await hashPassword(newPassword);
- const viewPassword = encryptPasswordForView(newPassword);
-
await pool.request()
.input('userId', sql.Int, user.UserId)
.input('password', sql.NVarChar, hashedPassword)
- .input('viewPassword', sql.NVarChar, viewPassword)
.query(`UPDATE Users
SET Password = @password,
- ViewPassword = @viewPassword,
+ ViewPassword = NULL,
PasswordResetToken = NULL,
PasswordResetTokenExpires = NULL
WHERE UserId = @userId`);
+ await invalidateUserSessions(user.UserId);
+
res.json({ success: true, message: 'Password reset successful. You can sign in now.' });
} catch (err) {
console.error('Reset password error:', err.message);
@@ -3548,6 +3974,29 @@ app.post('/api/auth/reset-password', async (req, res) => {
}
});
+// Public, non-sensitive health check for container monitoring.
+app.get('/api/health', (req, res) => {
+ res.json({ status: 'ok', timestamp: new Date().toISOString() });
+});
+
+app.get('/api/auth/session', requireAuth, (req, res) => {
+ res.json({ success: true, user: req.user });
+});
+
+app.post('/api/auth/logout', requireAuth, async (req, res) => {
+ try {
+ await destroyAuthSession(req, res);
+ res.json({ success: true, message: 'Signed out' });
+ } catch (err) {
+ console.error('Logout error:', err.message);
+ clearSessionCookie(res);
+ res.status(500).json({ success: false, message: 'Unable to sign out cleanly' });
+ }
+});
+
+// Every API declared below this point requires a valid server-side session.
+app.use('/api', requireAuth);
+
// Middleware for role-based access control
function normalizeRole(value) {
return String(value || '').trim().toLowerCase();
@@ -3557,7 +4006,7 @@ function requireRoles(roles = [], message = 'Access denied') {
const allowedRoles = new Set(roles.map(normalizeRole));
return (req, res, next) => {
- const userRole = normalizeRole(req.headers['x-user-role'] || req.query.userRole);
+ const userRole = getRequesterRole(req);
if (!allowedRoles.has(userRole)) {
return res.status(403).json({ success: false, message });
}
@@ -3579,7 +4028,7 @@ app.get('/api/roles', async (req, res) => {
.query('SELECT RoleId, RoleName, Description, CreatedDate FROM Roles ORDER BY RoleName');
res.json({ success: true, data: result.recordset });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -3596,7 +4045,7 @@ app.get('/api/roles/:id', async (req, res) => {
res.status(404).json({ success: false, message: 'Role not found' });
}
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -3621,7 +4070,7 @@ app.post('/api/roles', requireAdmin, async (req, res) => {
res.json({ success: true, message: 'Role created', roleId: result.recordset[0].RoleId });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -3641,7 +4090,7 @@ app.put('/api/roles/:id', requireAdmin, async (req, res) => {
res.json({ success: true, message: 'Role updated' });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -3666,7 +4115,7 @@ app.delete('/api/roles/:id', requireAdmin, async (req, res) => {
res.json({ success: true, message: 'Role deleted' });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -3685,7 +4134,7 @@ app.get('/api/users', async (req, res) => {
ORDER BY u.CreatedDate DESC`);
res.json({ success: true, data: result.recordset });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -3709,7 +4158,7 @@ app.get('/api/users/me', async (req, res) => {
res.json({ success: true, data: result.recordset[0] });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -3752,9 +4201,13 @@ app.put('/api/users/me', async (req, res) => {
const emailChanged = String(existingUser.Email || '').toLowerCase() !== incomingEmail;
const shouldChangePassword = newPassword.length > 0;
- if (shouldChangePassword) {
+ if (shouldChangePassword && (newPassword.length < 12 || newPassword.length > 256)) {
+ return res.status(400).json({ success: false, message: 'New password must be between 12 and 256 characters' });
+ }
+
+ if (shouldChangePassword || emailChanged) {
if (!currentPassword) {
- return res.status(400).json({ success: false, message: 'Current password is required to set a new password' });
+ return res.status(400).json({ success: false, message: 'Current password is required for security-sensitive profile changes' });
}
const isCurrentPasswordValid = await verifyPassword(currentPassword, existingUser.Password);
@@ -3789,15 +4242,13 @@ app.put('/api/users/me', async (req, res) => {
if (shouldChangePassword) {
const hashedPassword = await hashPassword(newPassword);
- const viewPassword = encryptPasswordForView(newPassword);
request.input('password', sql.NVarChar, hashedPassword);
- request.input('viewPassword', sql.NVarChar, viewPassword);
}
await request.query(`UPDATE Users
SET FullName = @fullname,
Email = @email
- ${shouldChangePassword ? ', Password = @password, ViewPassword = @viewPassword' : ''}
+ ${shouldChangePassword ? ', Password = @password, ViewPassword = NULL' : ''}
${emailChanged ? ', EmailVerified = 0, EmailVerifiedAt = NULL, EmailVerifyToken = @emailVerifyToken, EmailVerifyTokenExpires = DATEADD(MINUTE, @tokenTtlMinutes, DATEADD(HOUR, 7, SYSUTCDATETIME()))' : ''}
WHERE UserId = @userId`);
@@ -3827,11 +4278,19 @@ app.put('/api/users/me', async (req, res) => {
emailSent: emailChanged ? emailResult.sent : undefined
};
- if (emailChanged && emailResult.previewUrl) {
+ if (emailChanged) {
+ await invalidateUserSessions(userId);
+ clearSessionCookie(res);
+ payload.sessionEnded = true;
+ } else if (shouldChangePassword) {
+ await invalidateUserSessions(userId, req.authSessionId);
+ }
+
+ if (!IS_PRODUCTION && emailChanged && emailResult.previewUrl) {
payload.verificationPreviewUrl = emailResult.previewUrl;
}
- if (emailChanged && emailResult.reason && !emailResult.sent) {
+ if (!IS_PRODUCTION && emailChanged && emailResult.reason && !emailResult.sent) {
payload.emailError = emailResult.reason;
}
@@ -3847,28 +4306,23 @@ app.get('/api/users/:id', requireAdmin, async (req, res) => {
try {
const result = await pool.request()
.input('userId', sql.Int, req.params.id)
- .query(`SELECT u.*, r.RoleName FROM Users u
+ .query(`SELECT u.UserId, u.Username, u.Email, u.FullName, u.Role, u.RoleId,
+ u.Status, u.CreatedDate, u.LastLogin, u.IsActive,
+ u.EmailVerified, u.EmailVerifiedAt, r.RoleName
+ FROM Users u
LEFT JOIN Roles r ON u.RoleId = r.RoleId
WHERE u.UserId = @userId`);
if (result.recordset.length > 0) {
- const record = result.recordset[0];
- const viewPassword = decryptPasswordForView(record.ViewPassword || '');
- const fallbackPlainPassword = !isBcryptHash(record.Password) ? String(record.Password || '') : '';
- const plainPassword = viewPassword || fallbackPlainPassword;
-
- const userDetails = {
- ...record,
- Password: plainPassword,
- PasswordAvailable: Boolean(plainPassword)
- };
-
- res.json({ success: true, data: userDetails });
+ res.json({
+ success: true,
+ data: { ...result.recordset[0], PasswordAvailable: false }
+ });
} else {
res.status(404).json({ success: false, message: 'User not found' });
}
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -3881,8 +4335,14 @@ app.post('/api/users', requireAdmin, async (req, res) => {
return res.status(400).json({ success: false, message: 'Username and password are required' });
}
+ if (!/^[A-Za-z0-9._-]{3,50}$/.test(String(username).trim())) {
+ return res.status(400).json({ success: false, message: 'Username format is invalid' });
+ }
+ if (String(password).length < 12 || String(password).length > 256) {
+ return res.status(400).json({ success: false, message: 'Password must be between 12 and 256 characters' });
+ }
+
const hashedPassword = await hashPassword(password);
- const viewPassword = encryptPasswordForView(password);
const finalRoleId = roleId || 2; // Default to Guest role
// Get role name from Roles table
@@ -3899,11 +4359,10 @@ app.post('/api/users', requireAdmin, async (req, res) => {
.input('fullname', sql.NVarChar, fullname)
.input('roleId', sql.Int, finalRoleId)
.input('role', sql.NVarChar, roleName)
- .input('viewPassword', sql.NVarChar, viewPassword)
.query(`IF NOT EXISTS (SELECT * FROM Users WHERE Username = @username)
BEGIN
- INSERT INTO Users (Username, Password, ViewPassword, Email, FullName, RoleId, Role, IsActive)
- VALUES (@username, @password, @viewPassword, @email, @fullname, @roleId, @role, 1);
+ INSERT INTO Users (Username, Password, Email, FullName, RoleId, Role, IsActive, EmailVerified, EmailVerifiedAt)
+ VALUES (@username, @password, @email, @fullname, @roleId, @role, 1, 1, DATEADD(HOUR, 7, SYSUTCDATETIME()));
SELECT SCOPE_IDENTITY() as UserId
END
ELSE
@@ -3917,7 +4376,7 @@ app.post('/api/users', requireAdmin, async (req, res) => {
res.status(400).json({ success: false, message: 'Username already exists' });
}
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -3927,6 +4386,9 @@ app.put('/api/users/:id', requireAdmin, async (req, res) => {
const { email, fullname, roleId, status, isActive, password } = req.body;
const nextPassword = typeof password === 'string' ? password.trim() : '';
const shouldUpdatePassword = nextPassword.length > 0;
+ if (shouldUpdatePassword && (nextPassword.length < 12 || nextPassword.length > 256)) {
+ return res.status(400).json({ success: false, message: 'Password must be between 12 and 256 characters' });
+ }
// Get role name from Roles table
let roleName = '';
@@ -3948,9 +4410,7 @@ app.put('/api/users/:id', requireAdmin, async (req, res) => {
if (shouldUpdatePassword) {
const hashedPassword = await hashPassword(nextPassword);
- const viewPassword = encryptPasswordForView(nextPassword);
request.input('password', sql.NVarChar, hashedPassword);
- request.input('viewPassword', sql.NVarChar, viewPassword);
}
await request.query(`UPDATE Users
@@ -3960,33 +4420,51 @@ app.put('/api/users/:id', requireAdmin, async (req, res) => {
Role = @role,
Status = @status,
IsActive = @isActive
- ${shouldUpdatePassword ? ', Password = @password, ViewPassword = @viewPassword' : ''}
+ ${shouldUpdatePassword ? ', Password = @password, ViewPassword = NULL' : ''}
WHERE UserId = @userId`);
+ if (shouldUpdatePassword || !isActive) {
+ await invalidateUserSessions(Number(req.params.id));
+ }
+
res.json({ success: true, message: shouldUpdatePassword ? 'User updated and password changed' : 'User updated' });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ console.error('Update user error:', err.message);
+ res.status(500).json({ success: false, message: 'Unable to update user' });
}
});
// Delete user (Admin only)
app.delete('/api/users/:id', requireAdmin, async (req, res) => {
try {
- // Prevent deleting the current admin user (ID = 1)
- if (req.params.id === '1') {
+ const targetUserId = Number(req.params.id);
+ if (!Number.isInteger(targetUserId) || targetUserId <= 0) {
+ return res.status(400).json({ success: false, message: 'Invalid user id' });
+ }
+ if (targetUserId === getUserIdFromRequest(req)) {
return res.status(400).json({
success: false,
- message: 'Cannot delete the primary admin user'
+ message: 'You cannot delete your own signed-in account'
});
}
+ const adminCheck = await pool.request()
+ .input('userId', sql.Int, targetUserId)
+ .query(`SELECT TOP 1 LOWER(ISNULL(Role, '')) AS Role FROM Users WHERE UserId = @userId;
+ SELECT COUNT(*) AS ActiveAdminCount FROM Users WHERE LOWER(ISNULL(Role, '')) = 'admin' AND IsActive = 1;`);
+ const targetRole = adminCheck.recordsets?.[0]?.[0]?.Role;
+ const activeAdminCount = Number(adminCheck.recordsets?.[1]?.[0]?.ActiveAdminCount || 0);
+ if (targetRole === 'admin' && activeAdminCount <= 1) {
+ return res.status(400).json({ success: false, message: 'The last active administrator cannot be deleted' });
+ }
+
// Delete associated accounts first
await pool.request()
- .input('userId', sql.Int, req.params.id)
+ .input('userId', sql.Int, targetUserId)
.query('DELETE FROM Accounts WHERE UserId = @userId');
await pool.request()
- .input('userId', sql.Int, req.params.id)
+ .input('userId', sql.Int, targetUserId)
.query(`
IF COL_LENGTH('dbo.ConsumableExportHistory', 'RecipientUserId') IS NOT NULL
UPDATE ConsumableExportHistory SET RecipientUserId = NULL WHERE RecipientUserId = @userId
@@ -4000,12 +4478,13 @@ app.delete('/api/users/:id', requireAdmin, async (req, res) => {
// Then delete the user
await pool.request()
- .input('userId', sql.Int, req.params.id)
+ .input('userId', sql.Int, targetUserId)
.query('DELETE FROM Users WHERE UserId = @userId');
res.json({ success: true, message: 'User deleted' });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ console.error('Delete user error:', err.message);
+ res.status(500).json({ success: false, message: 'Unable to delete user' });
}
});
@@ -4020,14 +4499,18 @@ app.get('/api/applications', async (req, res) => {
.query('SELECT AppId, Name, Type, Status, Icon, Description, Url, CreatedDate, UpdatedDate FROM Applications ORDER BY Name');
res.json({ success: true, data: result.recordset });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
// Create application
-app.post('/api/applications', async (req, res) => {
+app.post('/api/applications', requireAdmin, async (req, res) => {
try {
const { name, type, status, icon, description, url } = req.body;
+ const safeUrl = normalizeOptionalHttpUrl(url);
+ if (!String(name || '').trim() || safeUrl === null) {
+ return res.status(400).json({ success: false, message: 'Application name and a valid HTTP(S) URL are required' });
+ }
const result = await pool.request()
.input('name', sql.NVarChar, name)
@@ -4035,21 +4518,25 @@ app.post('/api/applications', async (req, res) => {
.input('status', sql.NVarChar, status)
.input('icon', sql.NVarChar, icon)
.input('description', sql.NVarChar, description)
- .input('url', sql.NVarChar, url)
+ .input('url', sql.NVarChar, safeUrl)
.query(`INSERT INTO Applications (Name, Type, Status, Icon, Description, Url)
VALUES (@name, @type, @status, @icon, @description, @url);
SELECT SCOPE_IDENTITY() as AppId`);
res.json({ success: true, message: 'Application created', appId: result.recordset[0].AppId });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
// Update application
-app.put('/api/applications/:id', async (req, res) => {
+app.put('/api/applications/:id', requireAdmin, async (req, res) => {
try {
const { name, type, status, icon, description, url } = req.body;
+ const safeUrl = normalizeOptionalHttpUrl(url);
+ if (!String(name || '').trim() || safeUrl === null) {
+ return res.status(400).json({ success: false, message: 'Application name and a valid HTTP(S) URL are required' });
+ }
await pool.request()
.input('appId', sql.Int, req.params.id)
@@ -4058,7 +4545,7 @@ app.put('/api/applications/:id', async (req, res) => {
.input('status', sql.NVarChar, status)
.input('icon', sql.NVarChar, icon)
.input('description', sql.NVarChar, description)
- .input('url', sql.NVarChar, url)
+ .input('url', sql.NVarChar, safeUrl)
.query(`UPDATE Applications
SET Name = @name,
Type = @type,
@@ -4071,12 +4558,12 @@ app.put('/api/applications/:id', async (req, res) => {
res.json({ success: true, message: 'Application updated' });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
// Delete application
-app.delete('/api/applications/:id', async (req, res) => {
+app.delete('/api/applications/:id', requireAdmin, async (req, res) => {
try {
await pool.request()
.input('appId', sql.Int, req.params.id)
@@ -4084,7 +4571,7 @@ app.delete('/api/applications/:id', async (req, res) => {
res.json({ success: true, message: 'Application deleted' });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -4092,11 +4579,66 @@ app.delete('/api/applications/:id', async (req, res) => {
// API ROUTES - Accounts
// ==========================================
+function accountRecordForResponse(record) {
+ const storedPassword = String(record?.AccountPassword || '');
+
+ return {
+ ...record,
+ AccountPassword: '',
+ PasswordAvailable: Boolean(storedPassword)
+ };
+}
+
+function currentUserCanAccessAccount(req, ownerUserId) {
+ return getRequesterRole(req) === 'admin' || Number(ownerUserId) === getUserIdFromRequest(req);
+}
+
+app.get('/api/accounts/:id/secret', credentialRevealRateLimit, async (req, res) => {
+ try {
+ const accountId = Number(req.params.id);
+ if (!Number.isInteger(accountId) || accountId <= 0) {
+ return res.status(400).json({ success: false, message: 'Invalid account id' });
+ }
+
+ const result = await pool.request()
+ .input('accountId', sql.Int, accountId)
+ .query('SELECT TOP 1 UserId, AccountPassword FROM Accounts WHERE AccountId = @accountId');
+ const account = result.recordset[0];
+ if (!account) {
+ return res.status(404).json({ success: false, message: 'Account not found' });
+ }
+ if (!currentUserCanAccessAccount(req, account.UserId)) {
+ return res.status(403).json({ success: false, message: 'You cannot reveal credentials assigned to another user' });
+ }
+
+ const storedPassword = String(account.AccountPassword || '');
+ const password = storedPassword
+ ? (isEncryptedSensitiveValue(storedPassword) ? decryptSensitiveValue(storedPassword) : storedPassword)
+ : '';
+ if (storedPassword && password === null) {
+ return res.status(500).json({ success: false, message: 'Stored credential cannot be decrypted' });
+ }
+
+ res.json({ success: true, password: password || '' });
+ } catch (err) {
+ console.error('Reveal account password error:', err.message);
+ res.status(500).json({ success: false, message: 'Unable to reveal stored credential' });
+ }
+});
+
// Get accounts for a user
app.get('/api/accounts/user/:userId', async (req, res) => {
try {
+ const requestedUserId = Number(req.params.userId);
+ if (!Number.isInteger(requestedUserId) || requestedUserId <= 0) {
+ return res.status(400).json({ success: false, message: 'Invalid user id' });
+ }
+ if (!currentUserCanAccessAccount(req, requestedUserId)) {
+ return res.status(403).json({ success: false, message: 'You cannot access credentials assigned to another user' });
+ }
+
const result = await pool.request()
- .input('userId', sql.Int, req.params.userId)
+ .input('userId', sql.Int, requestedUserId)
.query(`SELECT a.*, app.Name as AppName, app.Type as AppType, u.Username
FROM Accounts a
JOIN Applications app ON a.AppId = app.AppId
@@ -4105,22 +4647,30 @@ app.get('/api/accounts/user/:userId', async (req, res) => {
ORDER BY a.CreatedDate DESC`);
res.json({ success: true, data: result.recordset });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
// Get all accounts (from all users)
app.get('/api/accounts/all', async (req, res) => {
try {
- const result = await pool.request()
+ const requesterId = getUserIdFromRequest(req);
+ const canViewAll = getRequesterRole(req) === 'admin';
+ const request = pool.request();
+ if (!canViewAll) {
+ request.input('requesterId', sql.Int, requesterId);
+ }
+ const result = await request
.query(`SELECT a.*, app.Name as AppName, app.Type as AppType, u.Username, u.FullName
FROM Accounts a
JOIN Applications app ON a.AppId = app.AppId
JOIN Users u ON a.UserId = u.UserId
+ ${canViewAll ? '' : 'WHERE a.UserId = @requesterId'}
ORDER BY a.CreatedDate DESC`);
- res.json({ success: true, data: result.recordset });
+ res.json({ success: true, data: result.recordset.map(accountRecordForResponse) });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ console.error('Get all accounts error:', err.message);
+ res.status(500).json({ success: false, message: 'Unable to load accounts' });
}
});
@@ -4128,12 +4678,20 @@ app.get('/api/accounts/all', async (req, res) => {
app.post('/api/accounts', async (req, res) => {
try {
const { userId, appId, accountUsername, accountPassword, email, accessLevel, notes } = req.body;
-
+ const requesterId = getUserIdFromRequest(req);
+ const targetUserId = getRequesterRole(req) === 'admin' ? Number(userId) : requesterId;
+ if (!Number.isInteger(targetUserId) || targetUserId <= 0 || !Number.isInteger(Number(appId)) || Number(appId) <= 0) {
+ return res.status(400).json({ success: false, message: 'User and application are required' });
+ }
+ if (!String(accountUsername || '').trim() || !String(accountPassword || '')) {
+ return res.status(400).json({ success: false, message: 'Account username and password are required' });
+ }
+
const result = await pool.request()
- .input('userId', sql.Int, userId)
- .input('appId', sql.Int, appId)
- .input('accountUsername', sql.NVarChar, accountUsername)
- .input('accountPassword', sql.NVarChar, accountPassword)
+ .input('userId', sql.Int, targetUserId)
+ .input('appId', sql.Int, Number(appId))
+ .input('accountUsername', sql.NVarChar, String(accountUsername).trim().slice(0, 100))
+ .input('accountPassword', sql.NVarChar(2048), encryptSensitiveValue(String(accountPassword)))
.input('email', sql.NVarChar, email)
.input('accessLevel', sql.NVarChar, accessLevel)
.input('notes', sql.NVarChar, notes)
@@ -4143,7 +4701,7 @@ app.post('/api/accounts', async (req, res) => {
res.json({ success: true, message: 'Account created', accountId: result.recordset[0].AccountId });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -4151,13 +4709,32 @@ app.post('/api/accounts', async (req, res) => {
app.put('/api/accounts/:id', async (req, res) => {
try {
const { userId, appId, accountUsername, accountPassword, email, accessLevel, notes } = req.body;
+ const accountId = Number(req.params.id);
+ if (!Number.isInteger(accountId) || accountId <= 0) {
+ return res.status(400).json({ success: false, message: 'Invalid account id' });
+ }
+
+ const existingResult = await pool.request()
+ .input('accountId', sql.Int, accountId)
+ .query('SELECT TOP 1 UserId, AccountPassword FROM Accounts WHERE AccountId = @accountId');
+ const existing = existingResult.recordset[0];
+ if (!existing) {
+ return res.status(404).json({ success: false, message: 'Account not found' });
+ }
+ if (!currentUserCanAccessAccount(req, existing.UserId)) {
+ return res.status(403).json({ success: false, message: 'You cannot update credentials assigned to another user' });
+ }
+
+ const targetUserId = getRequesterRole(req) === 'admin' ? Number(userId) : getUserIdFromRequest(req);
+ const nextPassword = String(accountPassword || '');
+ const storedPassword = nextPassword ? encryptSensitiveValue(nextPassword) : existing.AccountPassword;
await pool.request()
- .input('accountId', sql.Int, req.params.id)
- .input('userId', sql.Int, userId)
- .input('appId', sql.Int, appId)
+ .input('accountId', sql.Int, accountId)
+ .input('userId', sql.Int, targetUserId)
+ .input('appId', sql.Int, Number(appId))
.input('accountUsername', sql.NVarChar, accountUsername)
- .input('accountPassword', sql.NVarChar, accountPassword)
+ .input('accountPassword', sql.NVarChar(2048), storedPassword)
.input('email', sql.NVarChar, email)
.input('accessLevel', sql.NVarChar, accessLevel)
.input('notes', sql.NVarChar, notes)
@@ -4174,20 +4751,35 @@ app.put('/api/accounts/:id', async (req, res) => {
res.json({ success: true, message: 'Account updated' });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
// Delete account
app.delete('/api/accounts/:id', async (req, res) => {
try {
+ const accountId = Number(req.params.id);
+ if (!Number.isInteger(accountId) || accountId <= 0) {
+ return res.status(400).json({ success: false, message: 'Invalid account id' });
+ }
+ const existingResult = await pool.request()
+ .input('accountId', sql.Int, accountId)
+ .query('SELECT TOP 1 UserId FROM Accounts WHERE AccountId = @accountId');
+ const existing = existingResult.recordset[0];
+ if (!existing) {
+ return res.status(404).json({ success: false, message: 'Account not found' });
+ }
+ if (!currentUserCanAccessAccount(req, existing.UserId)) {
+ return res.status(403).json({ success: false, message: 'You cannot delete credentials assigned to another user' });
+ }
+
await pool.request()
- .input('accountId', sql.Int, req.params.id)
+ .input('accountId', sql.Int, accountId)
.query('DELETE FROM Accounts WHERE AccountId = @accountId');
res.json({ success: true, message: 'Account deleted' });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -4215,7 +4807,7 @@ app.get('/api/asset-departments', async (req, res) => {
res.json({ success: true, data: result.recordset });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -4258,7 +4850,7 @@ app.post('/api/asset-departments', requireAssetOrAdmin, async (req, res) => {
return res.status(409).json({ success: false, message: 'Phong ban da ton tai' });
}
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -4348,7 +4940,7 @@ app.put('/api/asset-departments/:id', requireAssetOrAdmin, async (req, res) => {
return res.status(409).json({ success: false, message: 'Phong ban da ton tai' });
}
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -4405,7 +4997,7 @@ app.delete('/api/asset-departments/:id', requireAssetOrAdmin, async (req, res) =
throw transactionErr;
}
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -4433,7 +5025,7 @@ app.get('/api/asset-projects', async (req, res) => {
res.json({ success: true, data: result.recordset });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -4476,7 +5068,7 @@ app.post('/api/asset-projects', requireAssetOrAdmin, async (req, res) => {
return res.status(409).json({ success: false, message: 'Du an da ton tai' });
}
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -4566,7 +5158,7 @@ app.put('/api/asset-projects/:id', requireAssetOrAdmin, async (req, res) => {
return res.status(409).json({ success: false, message: 'Du an da ton tai' });
}
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -4623,7 +5215,7 @@ app.delete('/api/asset-projects/:id', requireAssetOrAdmin, async (req, res) => {
throw transactionErr;
}
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -4633,7 +5225,7 @@ app.delete('/api/asset-projects/:id', requireAssetOrAdmin, async (req, res) => {
app.get('/api/asset-borrows', async (req, res) => {
try {
- const requesterRole = normalizeRole(req.headers['x-user-role'] || req.query.userRole);
+ const requesterRole = getRequesterRole(req);
const requesterId = getUserIdFromRequest(req);
const canManageRequests = requesterRole === 'admin' || requesterRole === 'asset';
@@ -4710,13 +5302,13 @@ app.get('/api/asset-borrows', async (req, res) => {
res.json({ success: true, data: result.recordset });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
app.get('/api/asset-borrows/:id/history', async (req, res) => {
try {
- const requesterRole = normalizeRole(req.headers['x-user-role'] || req.query.userRole);
+ const requesterRole = getRequesterRole(req);
const requesterId = getUserIdFromRequest(req);
const canManageRequests = requesterRole === 'admin' || requesterRole === 'asset';
const borrowId = Number(req.params.id);
@@ -4883,7 +5475,7 @@ app.get('/api/asset-borrows/:id/history', async (req, res) => {
}
});
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -5118,7 +5710,7 @@ app.post('/api/asset-borrows', async (req, res) => {
// Ignore rollback errors when transaction already finished.
}
}
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -5126,7 +5718,7 @@ app.post('/api/asset-borrows/:id/return', async (req, res) => {
const transaction = new sql.Transaction(pool);
try {
- const requesterRole = normalizeRole(req.headers['x-user-role'] || req.query.userRole);
+ const requesterRole = getRequesterRole(req);
const requesterId = getUserIdFromRequest(req);
const canManageRequests = requesterRole === 'admin' || requesterRole === 'asset';
const borrowId = Number(req.params.id);
@@ -5279,7 +5871,7 @@ app.post('/api/asset-borrows/:id/return', async (req, res) => {
} catch (rollbackErr) {
// Ignore rollback errors when transaction already finished.
}
- return res.status(500).json({ success: false, message: err.message });
+ return sendInternalError(res, err);
}
});
@@ -5293,7 +5885,8 @@ app.post('/api/asset-borrows/:id/process', requireAssetOrAdmin, async (req, res)
const processedBy = getUserIdFromRequest(req);
const processorName = String(
await getUserDisplayNameById(processedBy)
- || req.headers['x-user-role']
+ || req.user?.FullName
+ || req.user?.Username
|| 'Asset/Admin'
).trim();
@@ -5648,7 +6241,7 @@ app.post('/api/asset-borrows/:id/process', requireAssetOrAdmin, async (req, res)
} catch (rollbackErr) {
// Ignore rollback errors when transaction already finished.
}
- return res.status(500).json({ success: false, message: err.message });
+ return sendInternalError(res, err);
}
});
@@ -5656,7 +6249,7 @@ app.delete('/api/asset-borrows/:id', async (req, res) => {
const transaction = new sql.Transaction(pool);
try {
- const requesterRole = normalizeRole(req.headers['x-user-role'] || req.query.userRole);
+ const requesterRole = getRequesterRole(req);
const requesterId = getUserIdFromRequest(req);
const canManageRequests = requesterRole === 'admin' || requesterRole === 'asset';
const borrowId = Number(req.params.id);
@@ -5728,7 +6321,7 @@ app.delete('/api/asset-borrows/:id', async (req, res) => {
} catch (rollbackErr) {
// Ignore rollback errors when transaction already finished.
}
- return res.status(500).json({ success: false, message: err.message });
+ return sendInternalError(res, err);
}
});
@@ -5878,7 +6471,7 @@ async function applyConsumableReturn(transaction, {
app.get('/api/consumable-borrows', async (req, res) => {
try {
- const requesterRole = normalizeRole(req.headers['x-user-role'] || req.query.userRole);
+ const requesterRole = getRequesterRole(req);
const requesterId = getUserIdFromRequest(req);
const canManageRequests = requesterRole === 'admin' || requesterRole === 'asset';
@@ -5920,7 +6513,8 @@ app.get('/api/consumable-borrows', async (req, res) => {
res.json({ success: true, data: Array.isArray(result.recordset) ? result.recordset : [] });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ console.error('Create account error:', err.message);
+ res.status(500).json({ success: false, message: 'Unable to create account' });
}
});
@@ -6011,7 +6605,7 @@ app.post('/api/consumable-borrows', async (req, res) => {
});
} catch (err) {
console.error('Create consumable borrow request error:', err.message);
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -6144,7 +6738,7 @@ app.post('/api/consumable-exports/:id/return-request', async (req, res) => {
});
} catch (err) {
console.error('Create consumable return request error:', err.message);
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -6157,7 +6751,8 @@ app.post('/api/consumable-borrows/:id/process', requireAssetOrAdmin, async (req,
const rejectReason = String(req.body?.rejectReason || '').trim() || null;
const processedBy = getUserIdFromRequest(req);
const processedByName = await getUserDisplayNameById(processedBy)
- || String(req.headers['x-user-role'] || '').trim()
+ || req.user?.FullName
+ || req.user?.Username
|| 'Unknown';
if (!Number.isInteger(borrowRequestId) || borrowRequestId <= 0) {
@@ -6360,14 +6955,18 @@ app.post('/api/consumable-borrows/:id/process', requireAssetOrAdmin, async (req,
// Ignore rollback error, respond original error below.
}
}
- res.status(Number(err.statusCode) || 500).json({ success: false, message: err.message });
+ const statusCode = Number(err.statusCode) || 500;
+ if (statusCode >= 400 && statusCode < 500) {
+ return res.status(statusCode).json({ success: false, message: err.message });
+ }
+ sendInternalError(res, err);
}
});
app.delete('/api/consumable-borrows/:id', async (req, res) => {
try {
const borrowRequestId = Number(req.params.id);
- const requesterRole = normalizeRole(req.headers['x-user-role'] || req.query.userRole);
+ const requesterRole = getRequesterRole(req);
const requesterId = getUserIdFromRequest(req);
const canManageRequests = requesterRole === 'admin' || requesterRole === 'asset';
@@ -6394,7 +6993,8 @@ app.delete('/api/consumable-borrows/:id', async (req, res) => {
const deletedType = String(result.recordset?.[0]?.RequestType || 'borrow').trim().toLowerCase();
res.json({ success: true, message: deletedType === 'return' ? 'Đã hủy đơn trả' : 'Đã hủy đơn mượn' });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ console.error('Update account error:', err.message);
+ res.status(500).json({ success: false, message: 'Unable to update account' });
}
});
@@ -6524,9 +7124,10 @@ app.get('/api/consumables', async (req, res) => {
ORDER BY UpdatedDate DESC, ConsumableName ASC
`);
- res.json({ success: true, data: result.recordset });
+ res.json({ success: true, data: result.recordset.map(accountRecordForResponse) });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ console.error('Get user accounts error:', err.message);
+ res.status(500).json({ success: false, message: 'Unable to load accounts' });
}
});
@@ -6578,7 +7179,7 @@ app.post('/api/consumables', requireAssetOrAdmin, async (req, res) => {
return res.status(409).json({ success: false, message: 'Consumable code already exists' });
}
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -6638,7 +7239,7 @@ app.put('/api/consumables/:id', requireAssetOrAdmin, async (req, res) => {
return res.status(409).json({ success: false, message: 'Consumable code already exists' });
}
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -6659,14 +7260,15 @@ app.delete('/api/consumables/:id', requireAssetOrAdmin, async (req, res) => {
res.json({ success: true, message: 'Consumable deleted' });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ console.error('Delete account error:', err.message);
+ res.status(500).json({ success: false, message: 'Unable to delete account' });
}
});
app.get('/api/consumable-export-history', async (req, res) => {
try {
const limit = Math.min(parsePositiveInteger(req.query.limit, 300), 2000);
- const requesterRole = normalizeRole(req.headers['x-user-role'] || req.query.userRole);
+ const requesterRole = getRequesterRole(req);
const requesterId = getUserIdFromRequest(req);
const canManageAssets = requesterRole === 'admin' || requesterRole === 'asset';
if (!canManageAssets && (!Number.isInteger(requesterId) || requesterId <= 0)) {
@@ -6778,7 +7380,8 @@ app.get('/api/consumable-export-history', async (req, res) => {
data: Array.isArray(result.recordset) ? result.recordset : []
});
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ console.error('Get user details error:', err.message);
+ res.status(500).json({ success: false, message: 'Unable to load user details' });
}
});
@@ -6796,7 +7399,7 @@ app.post('/api/consumables/:id/export', requireAssetOrAdmin, async (req, res) =>
const projectName = String(req.body?.projectName || req.body?.project || '').trim();
const exportNote = String(req.body?.note || '').trim() || null;
const createdBy = getUserIdFromRequest(req);
- const exportedByName = await getUserDisplayNameById(createdBy) || String(req.headers['x-user-role'] || '').trim() || 'Unknown';
+ const exportedByName = await getUserDisplayNameById(createdBy) || req.user?.FullName || req.user?.Username || 'Unknown';
const exportedDate = new Date();
if (!Number.isInteger(consumableId) || consumableId <= 0) {
@@ -6999,7 +7602,7 @@ app.post('/api/consumables/:id/export', requireAssetOrAdmin, async (req, res) =>
// Ignore rollback error, respond original error below.
}
}
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -7012,7 +7615,8 @@ app.post('/api/consumable-exports/:id/return', requireAssetOrAdmin, async (req,
const returnNote = String(req.body?.note || '').trim() || null;
const createdBy = getUserIdFromRequest(req);
const returnedByName = await getUserDisplayNameById(createdBy)
- || String(req.headers['x-user-role'] || '').trim()
+ || req.user?.FullName
+ || req.user?.Username
|| 'Unknown';
if (!Number.isInteger(exportHistoryId) || exportHistoryId <= 0) {
@@ -7048,7 +7652,11 @@ app.post('/api/consumable-exports/:id/return', requireAssetOrAdmin, async (req,
// Ignore rollback error, respond original error below.
}
}
- res.status(Number(err.statusCode) || 500).json({ success: false, message: err.message });
+ const statusCode = Number(err.statusCode) || 500;
+ if (statusCode >= 400 && statusCode < 500) {
+ return res.status(statusCode).json({ success: false, message: err.message });
+ }
+ sendInternalError(res, err);
}
});
@@ -7176,7 +7784,7 @@ app.post('/api/consumables/import', requireAssetOrAdmin, upload.single('file'),
} catch (rollbackErr) {
// Ignore rollback errors if transaction is already completed.
}
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -7200,7 +7808,7 @@ app.get('/api/assets', async (req, res) => {
res.json({ success: true, data: result.recordset });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -7282,7 +7890,7 @@ app.get('/api/assets/search', async (req, res) => {
res.json({ success: true, data: rows, hasMore, total: totalCount });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -7312,7 +7920,7 @@ app.get('/api/assets/:id', async (req, res) => {
res.json({ success: true, data: result.recordset[0] });
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -7345,7 +7953,7 @@ app.get('/api/asset-export-history', requireAssetOrAdmin, async (req, res) => {
data: Array.isArray(result.recordset) ? result.recordset : []
});
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -7391,7 +7999,8 @@ app.get('/api/asset-damage-disposal-history', requireAssetOrAdmin, async (req, r
data: Array.isArray(result.recordset) ? result.recordset : []
});
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ console.error('Create user error:', err.message);
+ res.status(500).json({ success: false, message: 'Unable to create user' });
}
});
@@ -7630,7 +8239,7 @@ app.post('/api/assets/:id/damage-disposal', requireAssetOrAdmin, async (req, res
// Ignore rollback error, respond original error below.
}
}
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -7643,7 +8252,7 @@ app.post('/api/assets/:id/export', requireAssetOrAdmin, async (req, res) => {
const projectName = String(req.body?.projectName || '').trim() || null;
const exportNote = String(req.body?.note || '').trim() || null;
const createdBy = getUserIdFromRequest(req);
- const exportedByName = await getUserDisplayNameById(createdBy) || String(req.headers['x-user-role'] || '').trim() || 'Unknown';
+ const exportedByName = await getUserDisplayNameById(createdBy) || req.user?.FullName || req.user?.Username || 'Unknown';
const exportedDate = new Date();
if (!Number.isInteger(assetId) || assetId <= 0) {
@@ -7834,7 +8443,7 @@ app.post('/api/assets/:id/export', requireAssetOrAdmin, async (req, res) => {
// Ignore rollback error, respond original error below.
}
}
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -7898,7 +8507,7 @@ app.post('/api/assets', requireAssetOrAdmin, async (req, res) => {
return res.status(409).json({ success: false, message: 'Asset code already exists' });
}
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -7974,7 +8583,7 @@ app.put('/api/assets/:id', requireAssetOrAdmin, async (req, res) => {
return res.status(409).json({ success: false, message: 'Asset code already exists' });
}
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -8018,7 +8627,7 @@ app.delete('/api/assets/:id', requireAssetOrAdmin, async (req, res) => {
} catch (rollbackErr) {
// Ignore rollback errors when transaction already finished.
}
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -8224,7 +8833,7 @@ app.post('/api/assets/import', requireAssetOrAdmin, upload.single('file'), async
} catch (rollbackErr) {
// Ignore rollback errors if transaction is already completed.
}
- res.status(500).json({ success: false, message: err.message });
+ sendInternalError(res, err);
}
});
@@ -8233,7 +8842,7 @@ app.post('/api/assets/import', requireAssetOrAdmin, upload.single('file'), async
// ==========================================
// Get database information
-app.get('/api/database/info', async (req, res) => {
+app.get('/api/database/info', requireAdmin, async (req, res) => {
try {
const tables = await pool.request().query(`
SELECT TABLE_NAME as TableName,
@@ -8251,8 +8860,7 @@ app.get('/api/database/info', async (req, res) => {
res.json({
success: true,
- database: 'AccManager',
- server: '172.20.235.176',
+ database: DB_NAME,
tables: tables.recordset,
statistics: {
users: users.recordset[0].Count,
@@ -8263,22 +8871,21 @@ app.get('/api/database/info', async (req, res) => {
}
});
} catch (err) {
- res.status(500).json({ success: false, message: err.message });
+ console.error('Database info error:', err.message);
+ res.status(500).json({ success: false, message: 'Unable to load database information' });
}
});
-// Health check
-app.get('/api/health', (req, res) => {
- res.json({ status: 'OK', database: 'Connected' });
-});
-
// ==========================================
// Error Handling
// ==========================================
app.use((err, req, res, next) => {
- console.error(err);
- res.status(500).json({ success: false, message: err.message });
+ console.error('Unhandled request error:', err.message);
+ if (err instanceof multer.MulterError || err.message === 'Only .xls and .xlsx files are allowed') {
+ return res.status(400).json({ success: false, message: err.message });
+ }
+ res.status(500).json({ success: false, message: 'Internal server error' });
});
// ==========================================
@@ -8294,8 +8901,7 @@ async function startServer() {
console.log(`AccManager Backend Server`);
console.log(`========================================`);
console.log(`[OK] Server running on http://localhost:${PORT}`);
- console.log('[OK] Database: AccManager');
- console.log('[OK] Default admin: admin / admin');
+ console.log(`[OK] Database: ${DB_NAME}`);
console.log(`\nAPI Endpoints:`);
console.log(` POST /api/auth/login`);
console.log(` GET /api/database/info`);
@@ -8320,4 +8926,15 @@ process.on('SIGINT', async () => {
process.exit(0);
});
-startServer();
+if (require.main === module) {
+ startServer();
+}
+
+module.exports = {
+ app,
+ startServer,
+ encryptSensitiveValue,
+ decryptSensitiveValue,
+ hashSessionToken,
+ normalizeOptionalHttpUrl
+};
diff --git a/database/setup.sql b/database/setup.sql
index e8cc5d3..032ccd3 100644
--- a/database/setup.sql
+++ b/database/setup.sql
@@ -1,7 +1,6 @@
-- ===========================================
-- SQL Server Setup Script for AccManager
-- Database: AccManager
--- Server: 172.20.235.176
-- ===========================================
-- Create Database
@@ -39,6 +38,24 @@ BEGIN
PRINT 'Table Users created successfully.';
END
+-- Server-side authentication sessions. Only SHA-256 token hashes are stored.
+IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'AuthSessions')
+BEGIN
+ CREATE TABLE AuthSessions (
+ SessionId BIGINT PRIMARY KEY IDENTITY(1,1),
+ TokenHash CHAR(64) UNIQUE NOT NULL,
+ UserId INT NOT NULL,
+ ExpiresAt DATETIME2 NOT NULL,
+ CreatedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(),
+ UserAgent NVARCHAR(500),
+ IpAddress NVARCHAR(64),
+ FOREIGN KEY (UserId) REFERENCES Users(UserId) ON DELETE CASCADE
+ );
+ CREATE INDEX IX_AuthSessions_UserId ON AuthSessions(UserId);
+ CREATE INDEX IX_AuthSessions_ExpiresAt ON AuthSessions(ExpiresAt);
+ PRINT 'Table AuthSessions created successfully.';
+END
+
-- ===========================================
-- 2. CREATE APPLICATIONS TABLE
-- ===========================================
@@ -67,7 +84,7 @@ BEGIN
UserId INT NOT NULL,
AppId INT NOT NULL,
AccountUsername NVARCHAR(100),
- AccountPassword NVARCHAR(255),
+ AccountPassword NVARCHAR(2048),
Email NVARCHAR(100),
AccessLevel NVARCHAR(50),
Status NVARCHAR(20) DEFAULT 'Active',
@@ -80,6 +97,11 @@ BEGIN
PRINT 'Table Accounts created successfully.';
END
+IF COL_LENGTH('dbo.Accounts', 'AccountPassword') IS NOT NULL
+BEGIN
+ ALTER TABLE Accounts ALTER COLUMN AccountPassword NVARCHAR(2048) NULL;
+END
+
-- ===========================================
-- 4. CREATE ASSET INVENTORY TABLE
-- ===========================================
diff --git a/docker-compose.image.yml b/docker-compose.image.yml
index a8b0e23..54ea2ee 100644
--- a/docker-compose.image.yml
+++ b/docker-compose.image.yml
@@ -10,19 +10,31 @@ services:
PORT: 3000
TZ: ${TZ:-Asia/Ho_Chi_Minh}
APP_TIME_ZONE: ${APP_TIME_ZONE:-Asia/Ho_Chi_Minh}
- DB_SERVER: ${DB_SERVER:-172.20.235.176}
- DB_USER: ${DB_USER:-sa}
- DB_PASSWORD: ${DB_PASSWORD:-changeme}
+ DB_SERVER: ${DB_SERVER:?DB_SERVER is required}
+ DB_USER: ${DB_USER:?DB_USER is required}
+ DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required}
DB_NAME: ${DB_NAME:-AccManager}
DB_ENCRYPT: ${DB_ENCRYPT:-false}
DB_TRUST_CERTIFICATE: ${DB_TRUST_CERTIFICATE:-true}
DB_CONNECT_TIMEOUT: ${DB_CONNECT_TIMEOUT:-30000}
BCRYPT_ROUNDS: ${BCRYPT_ROUNDS:-12}
- APP_BASE_URL: ${APP_BASE_URL:-http://localhost:3000}
+ DATA_ENCRYPTION_SECRET: ${DATA_ENCRYPTION_SECRET:?DATA_ENCRYPTION_SECRET is required}
+ APP_BASE_URL: ${APP_BASE_URL:?APP_BASE_URL is required}
+ CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-}
+ COOKIE_SECURE: ${COOKIE_SECURE:-true}
+ SESSION_TTL_HOURS: ${SESSION_TTL_HOURS:-12}
+ REMEMBER_SESSION_TTL_DAYS: ${REMEMBER_SESSION_TTL_DAYS:-14}
+ ALLOW_SELF_REGISTRATION: ${ALLOW_SELF_REGISTRATION:-false}
+ TRUST_PROXY_HOPS: ${TRUST_PROXY_HOPS:-1}
+ INITIAL_ADMIN_USERNAME: ${INITIAL_ADMIN_USERNAME:-admin}
+ INITIAL_ADMIN_PASSWORD: ${INITIAL_ADMIN_PASSWORD:-}
+ INITIAL_ADMIN_EMAIL: ${INITIAL_ADMIN_EMAIL:-admin@accmanager.local}
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_SECURE: ${SMTP_SECURE:-false}
+ SMTP_REQUIRE_TLS: ${SMTP_REQUIRE_TLS:-true}
SMTP_USER: ${SMTP_USER:-}
SMTP_PASS: ${SMTP_PASS:-}
SMTP_FROM: ${SMTP_FROM:-}
EMAIL_VERIFY_TOKEN_TTL_MINUTES: ${EMAIL_VERIFY_TOKEN_TTL_MINUTES:-30}
+ PASSWORD_RESET_TOKEN_TTL_MINUTES: ${PASSWORD_RESET_TOKEN_TTL_MINUTES:-30}
diff --git a/docker-compose.yml b/docker-compose.yml
index 7285f39..e066a3b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -12,19 +12,31 @@ services:
PORT: 3000
TZ: ${TZ:-Asia/Ho_Chi_Minh}
APP_TIME_ZONE: ${APP_TIME_ZONE:-Asia/Ho_Chi_Minh}
- DB_SERVER: ${DB_SERVER:-172.20.235.176}
- DB_USER: ${DB_USER:-sa}
- DB_PASSWORD: ${DB_PASSWORD:-changeme}
+ DB_SERVER: ${DB_SERVER:?DB_SERVER is required}
+ DB_USER: ${DB_USER:?DB_USER is required}
+ DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required}
DB_NAME: ${DB_NAME:-AccManager}
DB_ENCRYPT: ${DB_ENCRYPT:-false}
DB_TRUST_CERTIFICATE: ${DB_TRUST_CERTIFICATE:-true}
DB_CONNECT_TIMEOUT: ${DB_CONNECT_TIMEOUT:-30000}
BCRYPT_ROUNDS: ${BCRYPT_ROUNDS:-12}
- APP_BASE_URL: ${APP_BASE_URL:-http://localhost:3000}
+ DATA_ENCRYPTION_SECRET: ${DATA_ENCRYPTION_SECRET:?DATA_ENCRYPTION_SECRET is required}
+ APP_BASE_URL: ${APP_BASE_URL:?APP_BASE_URL is required}
+ CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-}
+ COOKIE_SECURE: ${COOKIE_SECURE:-true}
+ SESSION_TTL_HOURS: ${SESSION_TTL_HOURS:-12}
+ REMEMBER_SESSION_TTL_DAYS: ${REMEMBER_SESSION_TTL_DAYS:-14}
+ ALLOW_SELF_REGISTRATION: ${ALLOW_SELF_REGISTRATION:-false}
+ TRUST_PROXY_HOPS: ${TRUST_PROXY_HOPS:-1}
+ INITIAL_ADMIN_USERNAME: ${INITIAL_ADMIN_USERNAME:-admin}
+ INITIAL_ADMIN_PASSWORD: ${INITIAL_ADMIN_PASSWORD:-}
+ INITIAL_ADMIN_EMAIL: ${INITIAL_ADMIN_EMAIL:-admin@accmanager.local}
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_SECURE: ${SMTP_SECURE:-false}
+ SMTP_REQUIRE_TLS: ${SMTP_REQUIRE_TLS:-true}
SMTP_USER: ${SMTP_USER:-}
SMTP_PASS: ${SMTP_PASS:-}
SMTP_FROM: ${SMTP_FROM:-}
EMAIL_VERIFY_TOKEN_TTL_MINUTES: ${EMAIL_VERIFY_TOKEN_TTL_MINUTES:-30}
+ PASSWORD_RESET_TOKEN_TTL_MINUTES: ${PASSWORD_RESET_TOKEN_TTL_MINUTES:-30}
diff --git a/package-lock.json b/package-lock.json
index f1ed615..14f3500 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,11 +12,13 @@
"bcrypt": "^6.0.0",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
- "express": "^4.18.2",
- "mssql": "^9.1.1",
- "multer": "^2.1.1",
- "nodemailer": "^8.0.4",
- "xlsx": "^0.18.5"
+ "express": "^5.2.1",
+ "express-rate-limit": "^8.6.0",
+ "helmet": "^8.3.0",
+ "mssql": "^12.7.0",
+ "multer": "^2.2.0",
+ "nodemailer": "^9.0.3",
+ "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
},
"devDependencies": {
"@tailwindcss/container-queries": "^0.1.1",
@@ -25,6 +27,9 @@
"nodemon": "^3.0.1",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.13"
+ },
+ "engines": {
+ "node": ">=22.0.0"
}
},
"node_modules/@alloc/quick-lru": {
@@ -41,50 +46,38 @@
}
},
"node_modules/@azure-rest/core-client": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.5.1.tgz",
- "integrity": "sha512-EHaOXW0RYDKS5CFffnixdyRPak5ytiCtU7uXDcP/uiY+A6jFRwNGzzJBiznkCzvi5EYpY+YWinieqHb0oY916A==",
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.8.0.tgz",
+ "integrity": "sha512-F1ybHeN+++QhyFCF/ehLUEvrOB6fehPdFBFtGdj0C3B2lpQ9zkPiO5JDgsqc6IfjuUe6b3dAbXK0a7+VgSGfhw==",
"license": "MIT",
"dependencies": {
"@azure/abort-controller": "^2.1.2",
"@azure/core-auth": "^1.10.0",
- "@azure/core-rest-pipeline": "^1.22.0",
+ "@azure/core-rest-pipeline": "^1.24.0",
"@azure/core-tracing": "^1.3.0",
"@typespec/ts-http-runtime": "^0.3.0",
"tslib": "^2.6.2"
},
"engines": {
- "node": ">=20.0.0"
+ "node": ">=22.0.0"
}
},
- "node_modules/@azure-rest/core-client/node_modules/@azure/abort-controller": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
- "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
+ "node_modules/@azure/abort-controller": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.2.0.tgz",
+ "integrity": "sha512-fNAjWnA/nZ2jz31kxR/AqRaUT8ewHBw/WuBIosK0moMy1C9e5ValbDfFdIxJzVOOYaYkV/b2F1S4H/aHiqfVQg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.6.2"
},
"engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@azure/abort-controller": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz",
- "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.2.0"
- },
- "engines": {
- "node": ">=12.0.0"
+ "node": ">=22.0.0"
}
},
"node_modules/@azure/core-auth": {
- "version": "1.10.1",
- "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz",
- "integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==",
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.11.0.tgz",
+ "integrity": "sha512-IUZydyTUkDnYdstOW9pFOOUQlBjAepK5teihDE3x6yxsPJs/hsAaaYpeGxdxrgtOiJbBKSjKW7MDk7AEhb4LRg==",
"license": "MIT",
"dependencies": {
"@azure/abort-controller": "^2.1.2",
@@ -92,25 +85,13 @@
"tslib": "^2.6.2"
},
"engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@azure/core-auth/node_modules/@azure/abort-controller": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
- "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
+ "node": ">=22.0.0"
}
},
"node_modules/@azure/core-client": {
- "version": "1.10.1",
- "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz",
- "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==",
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.11.0.tgz",
+ "integrity": "sha512-JjQWO6akOck45PH/XBrxzsQGAiKrfFl4m5iggJ0ItMIz5omRufOXWpqCPpdjKN3vKDzlSUvFjaMb7Zwf0gvAdA==",
"license": "MIT",
"dependencies": {
"@azure/abort-controller": "^2.1.2",
@@ -122,47 +103,7 @@
"tslib": "^2.6.2"
},
"engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@azure/core-client/node_modules/@azure/abort-controller": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
- "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@azure/core-http-compat": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.3.2.tgz",
- "integrity": "sha512-Tf6ltdKzOJEgxZeWLCjMxrxbodB/ZeCbzzA1A2qHbhzAjzjHoBVSUeSl/baT/oHAxhc4qdqVaDKnc2+iE932gw==",
- "license": "MIT",
- "dependencies": {
- "@azure/abort-controller": "^2.1.2"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@azure/core-client": "^1.10.0",
- "@azure/core-rest-pipeline": "^1.22.0"
- }
- },
- "node_modules/@azure/core-http-compat/node_modules/@azure/abort-controller": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
- "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
+ "node": ">=22.0.0"
}
},
"node_modules/@azure/core-lro": {
@@ -180,34 +121,22 @@
"node": ">=18.0.0"
}
},
- "node_modules/@azure/core-lro/node_modules/@azure/abort-controller": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
- "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
"node_modules/@azure/core-paging": {
- "version": "1.6.2",
- "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.6.2.tgz",
- "integrity": "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==",
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.7.0.tgz",
+ "integrity": "sha512-7GEAoIsaoBr6KELNRb8nypowCqvk8dnCHFCYg4XD4lOQGY2GqjQg5IhkRjyBFRO18CGSMq05PaNqSOE9GQro3g==",
"license": "MIT",
"dependencies": {
"tslib": "^2.6.2"
},
"engines": {
- "node": ">=18.0.0"
+ "node": ">=22.0.0"
}
},
"node_modules/@azure/core-rest-pipeline": {
- "version": "1.23.0",
- "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.23.0.tgz",
- "integrity": "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ==",
+ "version": "1.25.0",
+ "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.25.0.tgz",
+ "integrity": "sha512-bMs8ekJLjX8wPV+9IPBges1SLPyuDtE9g5gLDWOpxzKcoOFQnpLGkbcT1tdw3FaAmDS1gnPmMmJ6y/T5B96kIA==",
"license": "MIT",
"dependencies": {
"@azure/abort-controller": "^2.1.2",
@@ -219,37 +148,25 @@
"tslib": "^2.6.2"
},
"engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@azure/core-rest-pipeline/node_modules/@azure/abort-controller": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
- "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
+ "node": ">=22.0.0"
}
},
"node_modules/@azure/core-tracing": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.3.1.tgz",
- "integrity": "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.4.0.tgz",
+ "integrity": "sha512-eGwxD0AtncrxeBM4tG8R55Pc3rdX1hNW2WibJAgYpCVA6E93mvvVH+LcssoVjOBrSKWS55yEIHsk0X8ctHmfOQ==",
"license": "MIT",
"dependencies": {
"tslib": "^2.6.2"
},
"engines": {
- "node": ">=20.0.0"
+ "node": ">=22.0.0"
}
},
"node_modules/@azure/core-util": {
- "version": "1.13.1",
- "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz",
- "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==",
+ "version": "1.14.0",
+ "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.14.0.tgz",
+ "integrity": "sha512-9n2pWK61veAuN0V20t9lOuoV4CFMdyAZ1ygZzvBGk/pBBJRib/PjL9PLXa/aI2CcPpyHfqVsxxqLCYl6uZlfDw==",
"license": "MIT",
"dependencies": {
"@azure/abort-controller": "^2.1.2",
@@ -257,57 +174,40 @@
"tslib": "^2.6.2"
},
"engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@azure/core-util/node_modules/@azure/abort-controller": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
- "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
+ "node": ">=22.0.0"
}
},
"node_modules/@azure/identity": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-2.1.0.tgz",
- "integrity": "sha512-BPDz1sK7Ul9t0l9YKLEa8PHqWU4iCfhGJ+ELJl6c8CP3TpJt2urNCbm0ZHsthmxRsYoMPbz2Dvzj30zXZVmAFw==",
- "license": "MIT",
- "dependencies": {
- "@azure/abort-controller": "^1.0.0",
- "@azure/core-auth": "^1.3.0",
- "@azure/core-client": "^1.4.0",
- "@azure/core-rest-pipeline": "^1.1.0",
- "@azure/core-tracing": "^1.0.0",
- "@azure/core-util": "^1.0.0",
- "@azure/logger": "^1.0.0",
- "@azure/msal-browser": "^2.26.0",
- "@azure/msal-common": "^7.0.0",
- "@azure/msal-node": "^1.10.0",
- "events": "^3.0.0",
- "jws": "^4.0.0",
- "open": "^8.0.0",
- "stoppable": "^1.1.0",
- "tslib": "^2.2.0",
- "uuid": "^8.3.0"
- },
- "engines": {
- "node": ">=12.0.0"
- }
- },
- "node_modules/@azure/keyvault-common": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@azure/keyvault-common/-/keyvault-common-2.0.0.tgz",
- "integrity": "sha512-wRLVaroQtOqfg60cxkzUkGKrKMsCP6uYXAOomOIysSMyt1/YM0eUn9LqieAWM8DLcU4+07Fio2YGpPeqUbpP9w==",
+ "version": "4.13.1",
+ "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.1.tgz",
+ "integrity": "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw==",
"license": "MIT",
"dependencies": {
+ "@azure/abort-controller": "^2.0.0",
+ "@azure/core-auth": "^1.9.0",
+ "@azure/core-client": "^1.9.2",
+ "@azure/core-rest-pipeline": "^1.17.0",
+ "@azure/core-tracing": "^1.0.0",
+ "@azure/core-util": "^1.11.0",
+ "@azure/logger": "^1.0.0",
+ "@azure/msal-browser": "^5.5.0",
+ "@azure/msal-node": "^5.1.0",
+ "open": "^10.1.0",
+ "tslib": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@azure/keyvault-common": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@azure/keyvault-common/-/keyvault-common-2.1.0.tgz",
+ "integrity": "sha512-aCDidWuKY06LWQ4x7/8TIXK6iRqTaRWRL3t7T+LC+j1b07HtoIsOxP/tU90G4jCSBn5TAyUTCtA4MS/y5Hudaw==",
+ "license": "MIT",
+ "dependencies": {
+ "@azure-rest/core-client": "^2.3.3",
"@azure/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.3.0",
- "@azure/core-client": "^1.5.0",
"@azure/core-rest-pipeline": "^1.8.0",
"@azure/core-tracing": "^1.0.0",
"@azure/core-util": "^1.10.0",
@@ -315,121 +215,76 @@
"tslib": "^2.2.0"
},
"engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@azure/keyvault-common/node_modules/@azure/abort-controller": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
- "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
+ "node": ">=20.0.0"
}
},
"node_modules/@azure/keyvault-keys": {
- "version": "4.10.0",
- "resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.10.0.tgz",
- "integrity": "sha512-eDT7iXoBTRZ2n3fLiftuGJFD+yjkiB1GNqzU2KbY1TLYeXeSPVTVgn2eJ5vmRTZ11978jy2Kg2wI7xa9Tyr8ag==",
+ "version": "4.10.2",
+ "resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.10.2.tgz",
+ "integrity": "sha512-VmUSLbXRAbSzDD8grXHGPaknYs0SKr3yuf6U+d4XMpX4XuVYskNqbTTwXce0zR1LyxfTZm9rWEBcvs3vdYwCmQ==",
"license": "MIT",
"dependencies": {
"@azure-rest/core-client": "^2.3.3",
"@azure/abort-controller": "^2.1.2",
"@azure/core-auth": "^1.9.0",
- "@azure/core-http-compat": "^2.2.0",
"@azure/core-lro": "^2.7.2",
"@azure/core-paging": "^1.6.2",
"@azure/core-rest-pipeline": "^1.19.0",
"@azure/core-tracing": "^1.2.0",
"@azure/core-util": "^1.11.0",
- "@azure/keyvault-common": "^2.0.0",
+ "@azure/keyvault-common": "^2.1.0",
"@azure/logger": "^1.1.4",
"tslib": "^2.8.1"
},
"engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@azure/keyvault-keys/node_modules/@azure/abort-controller": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
- "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
+ "node": ">=20.0.0"
}
},
"node_modules/@azure/logger": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz",
- "integrity": "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.4.0.tgz",
+ "integrity": "sha512-rbAE25KUfjU/s3XHUdJgceoCP5dEOpMx85J04kF+QMdta73XkuG9JGHHinch+XIoKpBdqljin+KqURpJriSzLA==",
"license": "MIT",
"dependencies": {
"@typespec/ts-http-runtime": "^0.3.0",
"tslib": "^2.6.2"
},
"engines": {
- "node": ">=20.0.0"
+ "node": ">=22.0.0"
}
},
"node_modules/@azure/msal-browser": {
- "version": "2.39.0",
- "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.39.0.tgz",
- "integrity": "sha512-kks/n2AJzKUk+DBqZhiD+7zeQGBl+WpSOQYzWy6hff3bU0ZrYFqr4keFLlzB5VKuKZog0X59/FGHb1RPBDZLVg==",
+ "version": "5.17.1",
+ "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.17.1.tgz",
+ "integrity": "sha512-zBhRGzABKSI7hfWh5EaZmril5ybZ7imBN1qEZl5sDTaelr+l8SnPjZO50Q4dnKnm347YPIlBMSnXKZyh3Yu5DQ==",
"license": "MIT",
"dependencies": {
- "@azure/msal-common": "13.3.3"
+ "@azure/msal-common": "16.11.2"
},
"engines": {
"node": ">=0.8.0"
}
},
- "node_modules/@azure/msal-browser/node_modules/@azure/msal-common": {
- "version": "13.3.3",
- "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-13.3.3.tgz",
- "integrity": "sha512-n278DdCXKeiWhLwhEL7/u9HRMyzhUXLefeajiknf6AmEedoiOiv2r5aRJ7LXdT3NGPyubkdIbthaJlVtmuEqvA==",
- "license": "MIT",
- "engines": {
- "node": ">=0.8.0"
- }
- },
"node_modules/@azure/msal-common": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-7.6.0.tgz",
- "integrity": "sha512-XqfbglUTVLdkHQ8F9UQJtKseRr3sSnr9ysboxtoswvaMVaEfvyLtMoHv9XdKUfOc0qKGzNgRFd9yRjIWVepl6Q==",
+ "version": "16.11.2",
+ "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.11.2.tgz",
+ "integrity": "sha512-yDhtBOGDCdK9ipQ9g3+wmlMEPnZx2pXaDicDd9jYyR1L+7lEbvEohTDmF5qejZDutZY3m9pWPxeYxzNC701A2w==",
"license": "MIT",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@azure/msal-node": {
- "version": "1.18.4",
- "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.18.4.tgz",
- "integrity": "sha512-Kc/dRvhZ9Q4+1FSfsTFDME/v6+R2Y1fuMty/TfwqE5p9GTPw08BPbKgeWinE8JRHRp+LemjQbUZsn4Q4l6Lszg==",
- "deprecated": "A newer major version of this library is available. Please upgrade to the latest available version.",
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.4.1.tgz",
+ "integrity": "sha512-yqgoyOIMCH7TNaSLMBTP+4LUlbMMf1zgC8nzOFG95lmW82CmsAEtUT0J93e4BdqDcnX5qle/9X+yb7A8Mw9M0g==",
"license": "MIT",
"dependencies": {
- "@azure/msal-common": "13.3.1",
- "jsonwebtoken": "^9.0.0",
- "uuid": "^8.3.0"
+ "@azure/msal-common": "16.11.2",
+ "jsonwebtoken": "^9.0.0"
},
"engines": {
- "node": "10 || 12 || 14 || 16 || 18"
- }
- },
- "node_modules/@azure/msal-node/node_modules/@azure/msal-common": {
- "version": "13.3.1",
- "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-13.3.1.tgz",
- "integrity": "sha512-Lrk1ozoAtaP/cp53May3v6HtcFSVxdFrg2Pa/1xu5oIvsIwhxW6zSPibKefCOVgd5osgykMi5jjcZHv8XkzZEQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.8.0"
+ "node": ">=20"
}
},
"node_modules/@jridgewell/gen-mapping": {
@@ -472,9 +327,9 @@
}
},
"node_modules/@js-joda/core": {
- "version": "5.7.0",
- "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.7.0.tgz",
- "integrity": "sha512-WBu4ULVVxySLLzK1Ppq+OdfP+adRS4ntmDQT915rzDJ++i95gc2jZkM5B6LWEAwN3lGXpfie3yPABozdD3K3Vg==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-6.1.0.tgz",
+ "integrity": "sha512-H8NTMRDJqad/leyv/D/A3kSOsf5/58Ydj4DJGDyaCWk9OU/zuZOLhndVffJgQjsgrn5GC0znHMHie7TfvPPG4w==",
"license": "BSD-3-Clause"
},
"node_modules/@nodelib/fs.scandir": {
@@ -539,15 +394,33 @@
}
},
"node_modules/@tediousjs/connection-string": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/@tediousjs/connection-string/-/connection-string-0.5.0.tgz",
- "integrity": "sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@tediousjs/connection-string/-/connection-string-1.1.0.tgz",
+ "integrity": "sha512-z9ZBWEG+8pIB5V1zYzlRPXx0oRJ5H7coPnMQK8EZOw03UTPI9Umn6viL36f5w+CuqkKsnCM50RVStpjZmR0Bng==",
"license": "MIT"
},
+ "node_modules/@types/node": {
+ "version": "26.1.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz",
+ "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~8.3.0"
+ }
+ },
+ "node_modules/@types/readable-stream": {
+ "version": "4.0.24",
+ "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.24.tgz",
+ "integrity": "sha512-NRvUNC/JFGPJvqdAfEve8oginbM6V08u5NzLWpG8MwA2kTPOLnqk+wpwuPT+mp3aUsxyuT6m2gnrPuHYCruzEg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@typespec/ts-http-runtime": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.4.tgz",
- "integrity": "sha512-CI0NhTrz4EBaa0U+HaaUZrJhPoso8sG7ZFya8uQoBA57fjzrjRSv87ekCjLZOFExN+gXE/z0xuN2QfH4H2HrLQ==",
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.7.tgz",
+ "integrity": "sha512-JVUD8X2tfDMWjcjLs4yVxxVrS8yR5vnh386GAXT9Qj79nBxxXSaHFQZg5FweLmT8HlPQ3kii6noUB+Z9RN7DvQ==",
"license": "MIT",
"dependencies": {
"http-proxy-agent": "^7.0.0",
@@ -555,29 +428,57 @@
"tslib": "^2.6.2"
},
"engines": {
- "node": ">=20.0.0"
+ "node": ">=22.0.0"
+ }
+ },
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "license": "MIT",
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
}
},
"node_modules/accepts": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
- "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"license": "MIT",
"dependencies": {
- "mime-types": "~2.1.34",
- "negotiator": "0.6.3"
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
- "node_modules/adler-32": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
- "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
- "license": "Apache-2.0",
+ "node_modules/accepts/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
"engines": {
- "node": ">=0.8"
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/accepts/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/agent-base": {
@@ -623,58 +524,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/array-buffer-byte-length": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
- "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "is-array-buffer": "^3.0.5"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array-flatten": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
- "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
- "license": "MIT"
- },
- "node_modules/arraybuffer.prototype.slice": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
- "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==",
- "license": "MIT",
- "dependencies": {
- "array-buffer-byte-length": "^1.0.1",
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.5",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "is-array-buffer": "^3.0.4"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/async-function": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
- "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/autoprefixer": {
"version": "10.4.19",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
@@ -713,21 +562,6 @@
"postcss": "^8.1.0"
}
},
- "node_modules/available-typed-arrays": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
- "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
- "license": "MIT",
- "dependencies": {
- "possible-typed-array-names": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@@ -799,44 +633,126 @@
}
},
"node_modules/bl": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz",
- "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==",
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz",
+ "integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==",
"license": "MIT",
"dependencies": {
+ "@types/readable-stream": "^4.0.0",
"buffer": "^6.0.3",
"inherits": "^2.0.4",
- "readable-stream": "^3.4.0"
+ "readable-stream": "^4.2.0"
+ }
+ },
+ "node_modules/bl/node_modules/readable-stream": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
+ "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
+ "license": "MIT",
+ "dependencies": {
+ "abort-controller": "^3.0.0",
+ "buffer": "^6.0.3",
+ "events": "^3.3.0",
+ "process": "^0.11.10",
+ "string_decoder": "^1.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/body-parser": {
- "version": "1.20.4",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
- "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
+ "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
"license": "MIT",
"dependencies": {
- "bytes": "~3.1.2",
- "content-type": "~1.0.5",
- "debug": "2.6.9",
- "depd": "2.0.0",
- "destroy": "~1.2.0",
- "http-errors": "~2.0.1",
- "iconv-lite": "~0.4.24",
- "on-finished": "~2.4.1",
- "qs": "~6.14.0",
- "raw-body": "~2.5.3",
- "type-is": "~1.6.18",
- "unpipe": "~1.0.0"
+ "bytes": "^3.1.2",
+ "content-type": "^2.0.0",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.1",
+ "iconv-lite": "^0.7.2",
+ "on-finished": "^2.4.1",
+ "qs": "^6.15.2",
+ "raw-body": "^3.0.2",
+ "type-is": "^2.1.0"
},
"engines": {
- "node": ">= 0.8",
- "npm": "1.2.8000 || >= 1.4.16"
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/body-parser/node_modules/content-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/body-parser/node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/body-parser/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/body-parser/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/body-parser/node_modules/type-is": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
+ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^2.0.0",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "version": "5.0.7",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
+ "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -929,6 +845,21 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"license": "MIT"
},
+ "node_modules/bundle-name": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz",
+ "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==",
+ "license": "MIT",
+ "dependencies": {
+ "run-applescript": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -949,24 +880,6 @@
"node": ">= 0.8"
}
},
- "node_modules/call-bind": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
- "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.0",
- "es-define-property": "^1.0.0",
- "get-intrinsic": "^1.2.4",
- "set-function-length": "^1.2.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -1027,19 +940,6 @@
],
"license": "CC-BY-4.0"
},
- "node_modules/cfb": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
- "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
- "license": "Apache-2.0",
- "dependencies": {
- "adler-32": "~1.3.0",
- "crc-32": "~1.2.0"
- },
- "engines": {
- "node": ">=0.8"
- }
- },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -1065,15 +965,6 @@
"fsevents": "~2.3.2"
}
},
- "node_modules/codepage": {
- "version": "1.15.0",
- "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
- "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=0.8"
- }
- },
"node_modules/commander": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
@@ -1099,15 +990,16 @@
}
},
"node_modules/content-disposition": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
- "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
"license": "MIT",
- "dependencies": {
- "safe-buffer": "5.2.1"
- },
"engines": {
- "node": ">= 0.6"
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/content-type": {
@@ -1129,10 +1021,13 @@
}
},
"node_modules/cookie-signature": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
- "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
- "license": "MIT"
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
},
"node_modules/cors": {
"version": "2.8.6",
@@ -1151,18 +1046,6 @@
"url": "https://opencollective.com/express"
}
},
- "node_modules/crc-32": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
- "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
- "license": "Apache-2.0",
- "bin": {
- "crc32": "bin/crc32.njs"
- },
- "engines": {
- "node": ">=0.8"
- }
- },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -1176,107 +1059,61 @@
"node": ">=4"
}
},
- "node_modules/data-view-buffer": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
- "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "es-errors": "^1.3.0",
- "is-data-view": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/data-view-byte-length": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz",
- "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "es-errors": "^1.3.0",
- "is-data-view": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/inspect-js"
- }
- },
- "node_modules/data-view-byte-offset": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz",
- "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "is-data-view": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/define-data-property": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
- "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
- "license": "MIT",
- "dependencies": {
- "es-define-property": "^1.0.0",
- "es-errors": "^1.3.0",
- "gopd": "^1.0.1"
+ "ms": "^2.1.3"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/default-browser": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz",
+ "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==",
+ "license": "MIT",
+ "dependencies": {
+ "bundle-name": "^4.1.0",
+ "default-browser-id": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/default-browser-id": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz",
+ "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/define-lazy-prop": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
- "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
+ "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
"license": "MIT",
"engines": {
- "node": ">=8"
- }
- },
- "node_modules/define-properties": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
- "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
- "license": "MIT",
- "dependencies": {
- "define-data-property": "^1.0.1",
- "has-property-descriptors": "^1.0.0",
- "object-keys": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4"
+ "node": ">=12"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/depd": {
@@ -1288,16 +1125,6 @@
"node": ">= 0.8"
}
},
- "node_modules/destroy": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
- "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8",
- "npm": "1.2.8000 || >= 1.4.16"
- }
- },
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -1369,96 +1196,6 @@
"node": ">= 0.8"
}
},
- "node_modules/es-abstract": {
- "version": "1.24.1",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz",
- "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==",
- "license": "MIT",
- "dependencies": {
- "array-buffer-byte-length": "^1.0.2",
- "arraybuffer.prototype.slice": "^1.0.4",
- "available-typed-arrays": "^1.0.7",
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.4",
- "data-view-buffer": "^1.0.2",
- "data-view-byte-length": "^1.0.2",
- "data-view-byte-offset": "^1.0.1",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "es-set-tostringtag": "^2.1.0",
- "es-to-primitive": "^1.3.0",
- "function.prototype.name": "^1.1.8",
- "get-intrinsic": "^1.3.0",
- "get-proto": "^1.0.1",
- "get-symbol-description": "^1.1.0",
- "globalthis": "^1.0.4",
- "gopd": "^1.2.0",
- "has-property-descriptors": "^1.0.2",
- "has-proto": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "internal-slot": "^1.1.0",
- "is-array-buffer": "^3.0.5",
- "is-callable": "^1.2.7",
- "is-data-view": "^1.0.2",
- "is-negative-zero": "^2.0.3",
- "is-regex": "^1.2.1",
- "is-set": "^2.0.3",
- "is-shared-array-buffer": "^1.0.4",
- "is-string": "^1.1.1",
- "is-typed-array": "^1.1.15",
- "is-weakref": "^1.1.1",
- "math-intrinsics": "^1.1.0",
- "object-inspect": "^1.13.4",
- "object-keys": "^1.1.1",
- "object.assign": "^4.1.7",
- "own-keys": "^1.0.1",
- "regexp.prototype.flags": "^1.5.4",
- "safe-array-concat": "^1.1.3",
- "safe-push-apply": "^1.0.0",
- "safe-regex-test": "^1.1.0",
- "set-proto": "^1.0.0",
- "stop-iteration-iterator": "^1.1.0",
- "string.prototype.trim": "^1.2.10",
- "string.prototype.trimend": "^1.0.9",
- "string.prototype.trimstart": "^1.0.8",
- "typed-array-buffer": "^1.0.3",
- "typed-array-byte-length": "^1.0.3",
- "typed-array-byte-offset": "^1.0.4",
- "typed-array-length": "^1.0.7",
- "unbox-primitive": "^1.1.0",
- "which-typed-array": "^1.1.19"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/es-aggregate-error": {
- "version": "1.0.14",
- "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.14.tgz",
- "integrity": "sha512-3YxX6rVb07B5TV11AV5wsL7nQCHXNwoHPsQC8S4AmBiqYhyNCJ5BRKXkXyDJvs8QzXN20NgRtxe3dEEQD9NLHA==",
- "license": "MIT",
- "dependencies": {
- "define-data-property": "^1.1.4",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.24.0",
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2",
- "globalthis": "^1.0.4",
- "has-property-descriptors": "^1.0.2",
- "set-function-name": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -1478,9 +1215,9 @@
}
},
"node_modules/es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -1489,38 +1226,6 @@
"node": ">= 0.4"
}
},
- "node_modules/es-set-tostringtag": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
- "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-to-primitive": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
- "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==",
- "license": "MIT",
- "dependencies": {
- "is-callable": "^1.2.7",
- "is-date-object": "^1.0.5",
- "is-symbol": "^1.0.4"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -1546,6 +1251,15 @@
"node": ">= 0.6"
}
},
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@@ -1556,45 +1270,126 @@
}
},
"node_modules/express": {
- "version": "4.22.1",
- "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
- "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
"dependencies": {
- "accepts": "~1.3.8",
- "array-flatten": "1.1.1",
- "body-parser": "~1.20.3",
- "content-disposition": "~0.5.4",
- "content-type": "~1.0.4",
- "cookie": "~0.7.1",
- "cookie-signature": "~1.0.6",
- "debug": "2.6.9",
- "depd": "2.0.0",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "finalhandler": "~1.3.1",
- "fresh": "~0.5.2",
- "http-errors": "~2.0.0",
- "merge-descriptors": "1.0.3",
- "methods": "~1.1.2",
- "on-finished": "~2.4.1",
- "parseurl": "~1.3.3",
- "path-to-regexp": "~0.1.12",
- "proxy-addr": "~2.0.7",
- "qs": "~6.14.0",
- "range-parser": "~1.2.1",
- "safe-buffer": "5.2.1",
- "send": "~0.19.0",
- "serve-static": "~1.16.2",
- "setprototypeof": "1.2.0",
- "statuses": "~2.0.1",
- "type-is": "~1.6.18",
- "utils-merge": "1.0.1",
- "vary": "~1.1.2"
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
},
"engines": {
- "node": ">= 0.10.0"
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.0.tgz",
+ "integrity": "sha512-XKJXDsASUOo0LLtFwW5hCcQGH0N4WQc/Rn8/Pvoia+TJFOkkFPvrtW9lZOeeNcxQJspvOIERMwiRLsVFlhHEkA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "ip-address": "^10.2.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": ">= 4.11"
+ }
+ },
+ "node_modules/express/node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/express/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express/node_modules/type-is": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
+ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^2.0.0",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express/node_modules/type-is/node_modules/content-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
},
"funding": {
"type": "opencollective",
@@ -1642,36 +1437,24 @@
}
},
"node_modules/finalhandler": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
- "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
"license": "MIT",
"dependencies": {
- "debug": "2.6.9",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "on-finished": "~2.4.1",
- "parseurl": "~1.3.3",
- "statuses": "~2.0.2",
- "unpipe": "~1.0.0"
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
},
"engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/for-each": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
- "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
- "license": "MIT",
- "dependencies": {
- "is-callable": "^1.2.7"
- },
- "engines": {
- "node": ">= 0.4"
+ "node": ">= 18.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/forwarded": {
@@ -1683,15 +1466,6 @@
"node": ">= 0.6"
}
},
- "node_modules/frac": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
- "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=0.8"
- }
- },
"node_modules/fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
@@ -1707,12 +1481,12 @@
}
},
"node_modules/fresh": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
- "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
"engines": {
- "node": ">= 0.6"
+ "node": ">= 0.8"
}
},
"node_modules/fsevents": {
@@ -1739,44 +1513,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/function.prototype.name": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz",
- "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==",
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "functions-have-names": "^1.2.3",
- "hasown": "^2.0.2",
- "is-callable": "^1.2.7"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/functions-have-names": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
- "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/generator-function": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
- "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -1814,23 +1550,6 @@
"node": ">= 0.4"
}
},
- "node_modules/get-symbol-description": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
- "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@@ -1844,22 +1563,6 @@
"node": ">= 6"
}
},
- "node_modules/globalthis": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
- "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
- "license": "MIT",
- "dependencies": {
- "define-properties": "^1.2.1",
- "gopd": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -1872,18 +1575,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/has-bigints": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
- "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -1894,33 +1585,6 @@
"node": ">=4"
}
},
- "node_modules/has-property-descriptors": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
- "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
- "license": "MIT",
- "dependencies": {
- "es-define-property": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-proto": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz",
- "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==",
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
@@ -1933,21 +1597,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/has-tostringtag": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
- "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "license": "MIT",
- "dependencies": {
- "has-symbols": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@@ -1960,6 +1609,18 @@
"node": ">= 0.4"
}
},
+ "node_modules/helmet": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.3.0.tgz",
+ "integrity": "sha512-Qgpiaws3Sm30Av8Eah6sjMCZZwjlBu+E68rhpCWBshY1lb09HtLwj5GviX0OyQIn+ulUS0iX0AxN5n3tLZzz1w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/EvanHahn"
+ }
+ },
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
@@ -1993,29 +1654,6 @@
"node": ">= 14"
}
},
- "node_modules/http-proxy-agent/node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/http-proxy-agent/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
@@ -2029,39 +1667,20 @@
"node": ">= 14"
}
},
- "node_modules/https-proxy-agent/node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/https-proxy-agent/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
"node_modules/iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
+ "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
"license": "MIT",
"dependencies": {
- "safer-buffer": ">= 2.1.2 < 3"
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/ieee754": {
@@ -2097,18 +1716,13 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
- "node_modules/internal-slot": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
- "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
+ "node_modules/ip-address": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
+ "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
"license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "hasown": "^2.0.2",
- "side-channel": "^1.1.0"
- },
"engines": {
- "node": ">= 0.4"
+ "node": ">= 12"
}
},
"node_modules/ipaddr.js": {
@@ -2120,57 +1734,6 @@
"node": ">= 0.10"
}
},
- "node_modules/is-array-buffer": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
- "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "get-intrinsic": "^1.2.6"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-async-function": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
- "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==",
- "license": "MIT",
- "dependencies": {
- "async-function": "^1.0.0",
- "call-bound": "^1.0.3",
- "get-proto": "^1.0.1",
- "has-tostringtag": "^1.0.2",
- "safe-regex-test": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-bigint": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
- "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
- "license": "MIT",
- "dependencies": {
- "has-bigints": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -2184,34 +1747,6 @@
"node": ">=8"
}
},
- "node_modules/is-boolean-object": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
- "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "has-tostringtag": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-callable": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
- "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
@@ -2228,49 +1763,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-data-view": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz",
- "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "get-intrinsic": "^1.2.6",
- "is-typed-array": "^1.1.13"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-date-object": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
- "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "has-tostringtag": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/is-docker": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
- "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
+ "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
"license": "MIT",
"bin": {
"is-docker": "cli.js"
},
"engines": {
- "node": ">=8"
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -2286,40 +1788,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/is-finalizationregistry": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz",
- "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-generator-function": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz",
- "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.4",
- "generator-function": "^2.0.0",
- "get-proto": "^1.0.1",
- "has-tostringtag": "^1.0.2",
- "safe-regex-test": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -2333,28 +1801,22 @@
"node": ">=0.10.0"
}
},
- "node_modules/is-map": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
- "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+ "node_modules/is-inside-container": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
+ "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
"license": "MIT",
+ "dependencies": {
+ "is-docker": "^3.0.0"
+ },
+ "bin": {
+ "is-inside-container": "cli.js"
+ },
"engines": {
- "node": ">= 0.4"
+ "node": ">=14.16"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-negative-zero": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
- "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-number": {
@@ -2367,176 +1829,27 @@
"node": ">=0.12.0"
}
},
- "node_modules/is-number-object": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
- "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "has-tostringtag": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-regex": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
- "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "gopd": "^1.2.0",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-set": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
- "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-shared-array-buffer": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
- "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-string": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
- "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "has-tostringtag": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-symbol": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
- "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "has-symbols": "^1.1.0",
- "safe-regex-test": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-typed-array": {
- "version": "1.1.15",
- "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
- "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
- "license": "MIT",
- "dependencies": {
- "which-typed-array": "^1.1.16"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-weakmap": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
- "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-weakref": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz",
- "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-weakset": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz",
- "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "get-intrinsic": "^1.2.6"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
},
"node_modules/is-wsl": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
- "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz",
+ "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==",
"license": "MIT",
"dependencies": {
- "is-docker": "^2.0.0"
+ "is-inside-container": "^1.0.0"
},
"engines": {
- "node": ">=8"
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/isarray": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
- "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
- "license": "MIT"
- },
"node_modules/jiti": {
"version": "1.21.7",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
@@ -2553,12 +1866,6 @@
"integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==",
"license": "MIT"
},
- "node_modules/jsbi": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.2.tgz",
- "integrity": "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew==",
- "license": "Apache-2.0"
- },
"node_modules/jsonwebtoken": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
@@ -2581,12 +1888,6 @@
"npm": ">=6"
}
},
- "node_modules/jsonwebtoken/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
"node_modules/jwa": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
@@ -2686,10 +1987,13 @@
}
},
"node_modules/merge-descriptors": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
- "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
@@ -2704,15 +2008,6 @@
"node": ">= 8"
}
},
- "node_modules/methods": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
- "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@@ -2727,18 +2022,6 @@
"node": ">=8.6"
}
},
- "node_modules/mime": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
- "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
- "license": "MIT",
- "bin": {
- "mime": "cli.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -2787,58 +2070,34 @@
}
},
"node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "license": "MIT"
- },
- "node_modules/mssql": {
- "version": "9.3.2",
- "resolved": "https://registry.npmjs.org/mssql/-/mssql-9.3.2.tgz",
- "integrity": "sha512-XI5GOGCCSSNL8K2SSXg9HMyugJoCjLmrhiZfcZrJrJ2r3NfTcnz3Cegeg4m+xPkNVd0o3owsSL/NsDCFYfjOlw==",
- "license": "MIT",
- "dependencies": {
- "@tediousjs/connection-string": "^0.5.0",
- "commander": "^11.0.0",
- "debug": "^4.3.3",
- "rfdc": "^1.3.0",
- "tarn": "^3.0.2",
- "tedious": "^15.0.1"
- },
- "bin": {
- "mssql": "bin/mssql"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/mssql/node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/mssql/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
+ "node_modules/mssql": {
+ "version": "12.7.0",
+ "resolved": "https://registry.npmjs.org/mssql/-/mssql-12.7.0.tgz",
+ "integrity": "sha512-J6SJKXi1jYbhHjjooLNtPnX7+s3cq5IJ701Wgy/UW1SXRpgFlJJsYi3IPve9RVgCUkq0Cqv2aaaSJ4IXtIF3mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@tediousjs/connection-string": "^1.0.0",
+ "commander": "^11.0.0",
+ "debug": "^4.3.3",
+ "tarn": "^3.0.2",
+ "tedious": "^19.2.2 || ^20.0.0"
+ },
+ "bin": {
+ "mssql": "bin/mssql"
+ },
+ "engines": {
+ "node": ">=18.19.0"
+ }
+ },
"node_modules/multer": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz",
- "integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/multer/-/multer-2.2.0.tgz",
+ "integrity": "sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==",
"license": "MIT",
"dependencies": {
"append-field": "^1.0.0",
@@ -2867,9 +2126,9 @@
}
},
"node_modules/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "version": "3.3.16",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
+ "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
"dev": true,
"funding": [
{
@@ -2892,20 +2151,14 @@
"license": "MIT"
},
"node_modules/negotiator": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
- "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
- "node_modules/node-abort-controller": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
- "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==",
- "license": "MIT"
- },
"node_modules/node-addon-api": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz",
@@ -2934,9 +2187,9 @@
"license": "MIT"
},
"node_modules/nodemailer": {
- "version": "8.0.4",
- "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.4.tgz",
- "integrity": "sha512-k+jf6N8PfQJ0Fe8ZhJlgqU5qJU44Lpvp2yvidH3vp1lPnVQMgi4yEEMPXg5eJS1gFIJTVq1NHBk7Ia9ARdSBdQ==",
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.3.tgz",
+ "integrity": "sha512-n+YP+NKwR5zRWa60k3GiQ6Q3B4KXCoAw40dAKeCtYn020iNN74aWK2liXIC3ZEATeGql7we3tE3t8QwhY0eskw==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
@@ -2971,31 +2224,6 @@
"url": "https://opencollective.com/nodemon"
}
},
- "node_modules/nodemon/node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/nodemon/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -3047,35 +2275,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/object-keys": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
- "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/object.assign": {
- "version": "4.1.7",
- "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
- "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0",
- "has-symbols": "^1.1.0",
- "object-keys": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -3088,40 +2287,33 @@
"node": ">= 0.8"
}
},
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
"node_modules/open": {
- "version": "8.4.2",
- "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
- "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz",
+ "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==",
"license": "MIT",
"dependencies": {
- "define-lazy-prop": "^2.0.0",
- "is-docker": "^2.1.1",
- "is-wsl": "^2.2.0"
+ "default-browser": "^5.2.1",
+ "define-lazy-prop": "^3.0.0",
+ "is-inside-container": "^1.0.0",
+ "wsl-utils": "^0.1.0"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/own-keys": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
- "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==",
- "license": "MIT",
- "dependencies": {
- "get-intrinsic": "^1.2.6",
- "object-keys": "^1.1.1",
- "safe-push-apply": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -3139,10 +2331,14 @@
"license": "MIT"
},
"node_modules/path-to-regexp": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
- "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
- "license": "MIT"
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
},
"node_modules/picocolors": {
"version": "1.1.1",
@@ -3184,19 +2380,10 @@
"node": ">= 6"
}
},
- "node_modules/possible-typed-array-names": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
- "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/postcss": {
- "version": "8.4.38",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
- "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
+ "version": "8.5.19",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz",
+ "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==",
"dev": true,
"funding": [
{
@@ -3214,9 +2401,9 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.7",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.2.0"
+ "nanoid": "^3.3.12",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -3362,6 +2549,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -3382,22 +2578,14 @@
"dev": true,
"license": "MIT"
},
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/qs": {
- "version": "6.14.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
- "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+ "version": "6.15.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
+ "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
"license": "BSD-3-Clause",
"dependencies": {
- "side-channel": "^1.1.0"
+ "es-define-property": "^1.0.1",
+ "side-channel": "^1.1.1"
},
"engines": {
"node": ">=0.6"
@@ -3428,27 +2616,31 @@
"license": "MIT"
},
"node_modules/range-parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
+ "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/raw-body": {
- "version": "2.5.3",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
- "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
- "iconv-lite": "~0.4.24",
+ "iconv-lite": "~0.7.0",
"unpipe": "~1.0.0"
},
"engines": {
- "node": ">= 0.8"
+ "node": ">= 0.10"
}
},
"node_modules/read-cache": {
@@ -3488,48 +2680,6 @@
"node": ">=8.10.0"
}
},
- "node_modules/reflect.getprototypeof": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
- "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==",
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.9",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
- "get-intrinsic": "^1.2.7",
- "get-proto": "^1.0.1",
- "which-builtin-type": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/regexp.prototype.flags": {
- "version": "1.5.4",
- "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
- "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-errors": "^1.3.0",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "set-function-name": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/resolve": {
"version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
@@ -3562,11 +2712,33 @@
"node": ">=0.10.0"
}
},
- "node_modules/rfdc": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
- "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
- "license": "MIT"
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/run-applescript": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
+ "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
},
"node_modules/run-parallel": {
"version": "1.2.0",
@@ -3592,25 +2764,6 @@
"queue-microtask": "^1.2.2"
}
},
- "node_modules/safe-array-concat": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
- "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==",
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.2",
- "get-intrinsic": "^1.2.6",
- "has-symbols": "^1.1.0",
- "isarray": "^2.0.5"
- },
- "engines": {
- "node": ">=0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -3631,39 +2784,6 @@
],
"license": "MIT"
},
- "node_modules/safe-push-apply": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
- "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "isarray": "^2.0.5"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/safe-regex-test": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
- "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "is-regex": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -3683,94 +2803,73 @@
}
},
"node_modules/send": {
- "version": "0.19.2",
- "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
- "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
"license": "MIT",
"dependencies": {
- "debug": "2.6.9",
- "depd": "2.0.0",
- "destroy": "1.2.0",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "fresh": "~0.5.2",
- "http-errors": "~2.0.1",
- "mime": "1.6.0",
- "ms": "2.1.3",
- "on-finished": "~2.4.1",
- "range-parser": "~1.2.1",
- "statuses": "~2.0.2"
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
},
"engines": {
- "node": ">= 0.8.0"
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/send/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
+ "node_modules/send/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/send/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
},
"node_modules/serve-static": {
- "version": "1.16.3",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
- "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
"license": "MIT",
"dependencies": {
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "parseurl": "~1.3.3",
- "send": "~0.19.1"
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
},
"engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/set-function-length": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
- "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
- "license": "MIT",
- "dependencies": {
- "define-data-property": "^1.1.4",
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.4",
- "gopd": "^1.0.1",
- "has-property-descriptors": "^1.0.2"
+ "node": ">= 18"
},
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/set-function-name": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
- "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
- "license": "MIT",
- "dependencies": {
- "define-data-property": "^1.1.4",
- "es-errors": "^1.3.0",
- "functions-have-names": "^1.2.3",
- "has-property-descriptors": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/set-proto": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz",
- "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==",
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/setprototypeof": {
@@ -3780,14 +2879,14 @@
"license": "ISC"
},
"node_modules/side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
+ "object-inspect": "^1.13.4",
+ "side-channel-list": "^1.0.1",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
@@ -3799,13 +2898,13 @@
}
},
"node_modules/side-channel-list": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
+ "object-inspect": "^1.13.4"
},
"engines": {
"node": ">= 0.4"
@@ -3880,18 +2979,6 @@
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
"license": "BSD-3-Clause"
},
- "node_modules/ssf": {
- "version": "0.11.2",
- "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
- "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
- "license": "Apache-2.0",
- "dependencies": {
- "frac": "~1.1.2"
- },
- "engines": {
- "node": ">=0.8"
- }
- },
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
@@ -3901,29 +2988,6 @@
"node": ">= 0.8"
}
},
- "node_modules/stop-iteration-iterator": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
- "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "internal-slot": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/stoppable": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
- "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==",
- "license": "MIT",
- "engines": {
- "node": ">=4",
- "npm": ">=6"
- }
- },
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@@ -3941,62 +3005,6 @@
"safe-buffer": "~5.2.0"
}
},
- "node_modules/string.prototype.trim": {
- "version": "1.2.10",
- "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
- "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.2",
- "define-data-property": "^1.1.4",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.5",
- "es-object-atoms": "^1.0.0",
- "has-property-descriptors": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/string.prototype.trimend": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
- "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.2",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/string.prototype.trimstart": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
- "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/sucrase": {
"version": "3.35.1",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
@@ -4117,38 +3125,24 @@
}
},
"node_modules/tedious": {
- "version": "15.1.3",
- "resolved": "https://registry.npmjs.org/tedious/-/tedious-15.1.3.tgz",
- "integrity": "sha512-166EpRm5qknwhEisjZqz/mF7k14fXKJYHRg6XiAXVovd/YkyHJ3SG4Ppy89caPaNFfRr7PVYe+s4dAvKaCMFvw==",
+ "version": "20.0.0",
+ "resolved": "https://registry.npmjs.org/tedious/-/tedious-20.0.0.tgz",
+ "integrity": "sha512-bTR0aou0Ghucf0ytvZUJjnKHGKDV8tT57jPYtEkSpfTWFe++4uR1wxJLQ4mh5wlSvAYXzmgBxbI0vaE56qigXw==",
"license": "MIT",
"dependencies": {
- "@azure/identity": "^2.0.4",
- "@azure/keyvault-keys": "^4.4.0",
- "@js-joda/core": "^5.2.0",
- "bl": "^5.0.0",
- "es-aggregate-error": "^1.0.8",
- "iconv-lite": "^0.6.3",
+ "@azure/core-auth": "^1.10.1",
+ "@azure/identity": "^4.13.1",
+ "@azure/keyvault-keys": "^4.10.2",
+ "@js-joda/core": "^6.0.1",
+ "@types/node": ">=22",
+ "bl": "^6.1.4",
+ "iconv-lite": "^0.7.0",
"js-md4": "^0.3.2",
- "jsbi": "^4.3.0",
"native-duplexpair": "^1.0.0",
- "node-abort-controller": "^3.0.1",
- "punycode": "^2.1.0",
- "sprintf-js": "^1.1.2"
+ "sprintf-js": "^1.1.3"
},
"engines": {
- "node": ">=14"
- }
- },
- "node_modules/tedious/node_modules/iconv-lite": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
+ "node": ">=22"
}
},
"node_modules/thenify": {
@@ -4280,104 +3274,12 @@
"node": ">= 0.6"
}
},
- "node_modules/typed-array-buffer": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
- "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "es-errors": "^1.3.0",
- "is-typed-array": "^1.1.14"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/typed-array-byte-length": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
- "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "for-each": "^0.3.3",
- "gopd": "^1.2.0",
- "has-proto": "^1.2.0",
- "is-typed-array": "^1.1.14"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/typed-array-byte-offset": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
- "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
- "license": "MIT",
- "dependencies": {
- "available-typed-arrays": "^1.0.7",
- "call-bind": "^1.0.8",
- "for-each": "^0.3.3",
- "gopd": "^1.2.0",
- "has-proto": "^1.2.0",
- "is-typed-array": "^1.1.15",
- "reflect.getprototypeof": "^1.0.9"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/typed-array-length": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
- "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "for-each": "^0.3.3",
- "gopd": "^1.0.1",
- "is-typed-array": "^1.1.13",
- "possible-typed-array-names": "^1.0.0",
- "reflect.getprototypeof": "^1.0.6"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
"license": "MIT"
},
- "node_modules/unbox-primitive": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
- "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "has-bigints": "^1.0.2",
- "has-symbols": "^1.1.0",
- "which-boxed-primitive": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
@@ -4385,6 +3287,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/undici-types": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
+ "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
+ "license": "MIT"
+ },
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -4431,24 +3339,6 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
- "node_modules/utils-merge": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
- "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4.0"
- }
- },
- "node_modules/uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
- "license": "MIT",
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -4458,123 +3348,32 @@
"node": ">= 0.8"
}
},
- "node_modules/which-boxed-primitive": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
- "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
- "license": "MIT",
- "dependencies": {
- "is-bigint": "^1.1.0",
- "is-boolean-object": "^1.2.1",
- "is-number-object": "^1.1.1",
- "is-string": "^1.1.1",
- "is-symbol": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/which-builtin-type": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz",
- "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "function.prototype.name": "^1.1.6",
- "has-tostringtag": "^1.0.2",
- "is-async-function": "^2.0.0",
- "is-date-object": "^1.1.0",
- "is-finalizationregistry": "^1.1.0",
- "is-generator-function": "^1.0.10",
- "is-regex": "^1.2.1",
- "is-weakref": "^1.0.2",
- "isarray": "^2.0.5",
- "which-boxed-primitive": "^1.1.0",
- "which-collection": "^1.0.2",
- "which-typed-array": "^1.1.16"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/which-collection": {
+ "node_modules/wrappy": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
- "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/wsl-utils": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz",
+ "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==",
"license": "MIT",
"dependencies": {
- "is-map": "^2.0.3",
- "is-set": "^2.0.3",
- "is-weakmap": "^2.0.2",
- "is-weakset": "^2.0.3"
+ "is-wsl": "^3.1.0"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">=18"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/which-typed-array": {
- "version": "1.1.20",
- "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz",
- "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==",
- "license": "MIT",
- "dependencies": {
- "available-typed-arrays": "^1.0.7",
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.4",
- "for-each": "^0.3.5",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-tostringtag": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/wmf": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
- "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=0.8"
- }
- },
- "node_modules/word": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
- "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=0.8"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/xlsx": {
- "version": "0.18.5",
- "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
- "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
+ "version": "0.20.3",
+ "resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
+ "integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==",
"license": "Apache-2.0",
- "dependencies": {
- "adler-32": "~1.3.0",
- "cfb": "~1.2.1",
- "codepage": "~1.15.0",
- "crc-32": "~1.2.1",
- "ssf": "~0.11.2",
- "wmf": "~1.0.1",
- "word": "~0.3.0"
- },
"bin": {
"xlsx": "bin/xlsx.njs"
},
diff --git a/package.json b/package.json
index 1705ec0..e9048ff 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,8 @@
"scripts": {
"start": "node backend/server.js",
"dev": "nodemon backend/server.js",
+ "test": "node --test",
+ "check": "node --check backend/server.js && node --check public/js/app.js && npm audit",
"build:css": "tailwindcss -c tailwind.config.js -i ./public/css/tailwind.css -o ./public/css/main.css --minify",
"watch:css": "tailwindcss -c tailwind.config.js -i ./public/css/tailwind.css -o ./public/css/main.css --watch"
},
@@ -17,15 +19,20 @@
],
"author": "",
"license": "MIT",
+ "engines": {
+ "node": ">=22.0.0"
+ },
"dependencies": {
"bcrypt": "^6.0.0",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
- "express": "^4.18.2",
- "mssql": "^9.1.1",
- "multer": "^2.1.1",
- "nodemailer": "^8.0.4",
- "xlsx": "^0.18.5"
+ "express": "^5.2.1",
+ "express-rate-limit": "^8.6.0",
+ "helmet": "^8.3.0",
+ "mssql": "^12.7.0",
+ "multer": "^2.2.0",
+ "nodemailer": "^9.0.3",
+ "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
},
"devDependencies": {
"@tailwindcss/container-queries": "^0.1.1",
diff --git a/public/css/responsive.css b/public/css/responsive.css
index a60b21b..57e062b 100644
--- a/public/css/responsive.css
+++ b/public/css/responsive.css
@@ -150,6 +150,89 @@ textarea {
font-size: 1rem;
}
+ /* Keep the asset and consumable toolbars compact so the data remains
+ visible on tablets and phones. */
+ .asset-borrows-page .compact-page-actions,
+ .consumable-exports-page .compact-page-actions,
+ .assets-page .asset-header-actions,
+ .consumables-page .consumable-header-actions {
+ display: flex;
+ width: 100%;
+ max-width: 100%;
+ flex-flow: row nowrap;
+ gap: 0.5rem;
+ overflow-x: auto;
+ overscroll-behavior-x: contain;
+ padding: 0.125rem 0.125rem 0.375rem;
+ scrollbar-width: thin;
+ -webkit-overflow-scrolling: touch;
+ }
+
+ .asset-borrows-page .compact-page-actions > button,
+ .consumable-exports-page .compact-page-actions > button,
+ .assets-page .asset-header-actions > button,
+ .consumables-page .consumable-header-actions > button {
+ width: auto;
+ min-width: max-content;
+ min-height: 2.5rem;
+ flex: 0 0 auto;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ white-space: nowrap;
+ }
+
+ .compact-page-filters {
+ display: grid !important;
+ align-items: end !important;
+ gap: 0.625rem !important;
+ padding: 0.625rem;
+ border: 1px solid #e2e8f0;
+ border-radius: 0.75rem;
+ background: rgb(248 250 252 / 0.82);
+ }
+
+ .compact-page-filters.asset-borrow-filter-bar {
+ grid-template-columns: minmax(10rem, 0.35fr) minmax(0, 1fr);
+ }
+
+ .compact-page-filters.asset-filter-bar {
+ grid-template-columns: minmax(9rem, 0.3fr) minmax(14rem, 1fr) max-content;
+ }
+
+ .compact-page-filters.consumable-filter-bar {
+ grid-template-columns: minmax(8rem, 0.25fr) minmax(8rem, 0.25fr) minmax(14rem, 1fr);
+ }
+
+ .compact-page-filters.consumable-export-filter-bar {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .compact-page-filters > div {
+ width: auto;
+ min-width: 0;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 0.25rem;
+ }
+
+ .compact-page-filters input,
+ .compact-page-filters select {
+ width: 100%;
+ min-width: 0;
+ min-height: 2.5rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ }
+
+ .compact-page-filters .compact-filter-action {
+ width: auto;
+ min-width: max-content;
+ min-height: 2.5rem;
+ align-self: end;
+ justify-content: center;
+ white-space: nowrap;
+ }
+
.dashboard-stats,
.apps-stats {
gap: 0.75rem;
@@ -374,6 +457,29 @@ textarea {
grid-template-columns: minmax(0, 1fr) !important;
}
+ .compact-page-filters.asset-borrow-filter-bar {
+ grid-template-columns: minmax(0, 1fr);
+ }
+
+ .compact-page-filters.asset-filter-bar {
+ grid-template-columns: minmax(0, 1fr) max-content;
+ }
+
+ .compact-page-filters.asset-filter-bar .compact-filter-search {
+ grid-column: 1 / -1;
+ grid-row: 2;
+ }
+
+ .compact-page-filters.consumable-filter-bar,
+ .compact-page-filters.consumable-export-filter-bar {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .compact-page-filters.consumable-filter-bar .compact-filter-search,
+ .compact-page-filters.consumable-export-filter-bar .compact-filter-search {
+ grid-column: 1 / -1;
+ }
+
#mainContent table.mobile-card-table:not(.keep-table-mobile) > tbody {
padding: 0.625rem;
}
diff --git a/public/js/app.js b/public/js/app.js
index 37de47a..91f4d38 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -1,6 +1,27 @@
// VaultSentinel - Account Management Application
// Main JavaScript functionality
+const authenticatedFetch = window.fetch.bind(window);
+let authRedirectInProgress = false;
+window.fetch = async (input, init = {}) => {
+ const response = await authenticatedFetch(input, {
+ ...init,
+ credentials: init.credentials || 'same-origin'
+ });
+
+ const requestUrl = typeof input === 'string' ? input : String(input?.url || '');
+ const isAuthRequest = requestUrl.includes('/api/auth/login')
+ || requestUrl.includes('/api/auth/session')
+ || requestUrl.includes('/api/auth/logout');
+ if (response.status === 401 && !isAuthRequest && !authRedirectInProgress) {
+ authRedirectInProgress = true;
+ localStorage.removeItem('currentUser');
+ window.location.replace('../pages/login.html?reason=session-expired');
+ }
+
+ return response;
+};
+
const APP_TIME_ZONE = 'Asia/Ho_Chi_Minh';
const APP_DATE_FORMATTER = new Intl.DateTimeFormat('vi-VN', {
timeZone: APP_TIME_ZONE,
@@ -220,10 +241,7 @@ class AccountManager {
}
getAuthHeaders(includeJson = false) {
- const headers = {
- 'x-user-id': String(this.getUserId()),
- 'x-user-role': this.getCurrentUserRole()
- };
+ const headers = {};
if (includeJson) {
headers['Content-Type'] = 'application/json';
@@ -463,6 +481,15 @@ class AccountManager {
}
}
+ async fetchAccountSecret(accountId) {
+ const response = await fetch(`${this.apiBase}/accounts/${accountId}/secret`, { cache: 'no-store' });
+ const data = await response.json();
+ if (!response.ok || !data.success) {
+ throw new Error(data.message || 'Unable to reveal stored credential');
+ }
+ return String(data.password || '');
+ }
+
async fetchUsers() {
try {
const res = await fetch(`${this.apiBase}/users`);
@@ -2502,11 +2529,17 @@ class AccountManager {
return `${value.slice(0, 3)}*****`;
}
- handleLogout() {
+ async handleLogout() {
if (confirm('Are you sure you want to logout?')) {
- this.saveToStorage('currentUser', null);
- localStorage.clear();
- window.location.href = '../pages/login.html';
+ try {
+ await fetch(`${this.apiBase}/auth/logout`, { method: 'POST' });
+ } catch (error) {
+ console.error('Logout request failed:', error);
+ } finally {
+ this.saveToStorage('currentUser', null);
+ localStorage.removeItem('currentUser');
+ window.location.replace('../pages/login.html');
+ }
}
}
@@ -2643,13 +2676,14 @@ class AccountManager {
${pageInfo.data.map(acc => {
const isOwnAccount = acc.UserId == currentUserId;
+ const canAccessAccount = isOwnAccount || this.getCurrentUserRole() === 'admin';
const accountUsername = acc.AccountUsername || '-';
- const displayAccountUsername = isOwnAccount
+ const displayAccountUsername = canAccessAccount
? accountUsername
: this.maskForeignAccountUsername(accountUsername);
const createdDate = this.formatDateTime(acc.CreatedDate);
const updatedDate = this.formatDateTime(acc.UpdatedDate);
- const actionContent = isOwnAccount
+ const actionContent = canAccessAccount
? `
@@ -4244,7 +4278,7 @@ class AccountManager {
Theo dõi trạng thái đơn mượn và đơn trả tài sản.
-
+
-
+
Danh mục
-
+
Tìm kiếm
` : ''}
-
+
Tháng
-
+
Tìm kiếm
@@ -5478,7 +5512,7 @@ class AccountManager {
${pageDescription}
-
+
-
+
Người nhận
-
+
Tìm kiếm
-
+
Trạng thái
-
+
Tìm kiếm
-