From e8cfe6e7802fc3d3a5f80c374eaaca6604c23954 Mon Sep 17 00:00:00 2001 From: Nikhil Jadav Date: Fri, 21 Nov 2025 13:03:06 +1100 Subject: [PATCH 001/111] Homepage redesigned --- frontend/src/pages/Landing/LandingPage.css | 758 ++++++++++++------ frontend/src/pages/Landing/LandingPage.js | 153 +--- .../Landing/components/BenefitsSection.js | 64 ++ .../pages/Landing/components/CTASection.js | 25 + .../Landing/components/FeaturesSection.js | 71 ++ .../pages/Landing/components/HeroSection.js | 57 ++ .../pages/Landing/components/LandingFooter.js | 46 ++ .../pages/Landing/components/LandingHeader.js | 24 + .../pages/Landing/components/StatsSection.js | 25 + 9 files changed, 866 insertions(+), 357 deletions(-) create mode 100644 frontend/src/pages/Landing/components/BenefitsSection.js create mode 100644 frontend/src/pages/Landing/components/CTASection.js create mode 100644 frontend/src/pages/Landing/components/FeaturesSection.js create mode 100644 frontend/src/pages/Landing/components/HeroSection.js create mode 100644 frontend/src/pages/Landing/components/LandingFooter.js create mode 100644 frontend/src/pages/Landing/components/LandingHeader.js create mode 100644 frontend/src/pages/Landing/components/StatsSection.js diff --git a/frontend/src/pages/Landing/LandingPage.css b/frontend/src/pages/Landing/LandingPage.css index 7c5ae83c..1710b7be 100644 --- a/frontend/src/pages/Landing/LandingPage.css +++ b/frontend/src/pages/Landing/LandingPage.css @@ -1,343 +1,653 @@ -:root { - --bg1: #0d1b2a; - --bg2: #102a43; - --accent: #28e0d6; - --accent-900: #1fc9c0; - --text: #e9f0f5; - --muted: #b9c5d3; - --outline: #2b3a4a; - --card: #0f2334aa; - --shadow: 0 10px 30px rgba(0,0,0,.35), inset 0 0 60px rgba(255,255,255,.03); - --radius: 14px; - --maxw: 1150px; -} - - -* { box-sizing: border-box; } -html, body { height: 100%; } -body { - margin: 0; - font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; - color: var(--text); -} - - .landing-page { + --bg-dark: #0a1628; + --bg-darker: #0f1f38; + --bg-gradient: linear-gradient(135deg, #0a1628 0%, #162a4a 50%, #1e3a5f 100%); + --text-main: #ffffff; + --text-muted: #b0c4de; + --accent: #40e0d0; + --accent-strong: #1e90ff; + --border: rgba(64, 224, 208, 0.1); + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + color: var(--text-main); + background: var(--bg-dark); min-height: 100vh; - background: - radial-gradient(900px 600px at 10% 0%, #1f6e7f 0%, rgba(31,110,127,0) 55%), - radial-gradient(800px 500px at 95% 100%, #111827 0%, rgba(17,24,39,0) 60%), - linear-gradient(180deg, #14364a 0%, #0a1622 100%); - font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; } - -.landing-page .wrap { - max-width: none; - margin: 10px; - padding: 10px; +.landing-page * { + box-sizing: border-box; } - -.landing-page .nav { +.landing-page main { display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - position: relative; + flex-direction: column; + gap: 0; } -.landing-page .brand { +/* Header */ +.landing-header { + position: sticky; + top: 0; display: flex; + justify-content: space-between; align-items: center; - gap: 12px; + padding: 1.5rem 5%; + background: rgba(10, 22, 40, 0.95); + backdrop-filter: blur(12px); + border-bottom: 1px solid var(--border); + z-index: 5; } -.landing-page .logo-section { - position: absolute; - top: 20px; - left: 20px; - margin: 0; -} - -.landing-page .logo-img { - width: 160px; - height: auto; - border-radius: 15px; - box-shadow: 0 0 20px #0ff; +.landing-logo img { + height: 70px; + width: auto; display: block; + transition: transform 0.3s ease; } -.landing-page .brand-name { - font-weight: 700; - letter-spacing: .2px; - display: none; +.landing-logo img:hover { + transform: scale(1.04); } -.landing-page .navlinks { +.landing-nav { display: flex; align-items: center; - gap: 26px; + gap: 1.5rem; } -.landing-page .navlinks a, -.landing-page .navlinks button { - color: var(--text); +.landing-nav a, +.landing-nav .link-button { text-decoration: none; - font-weight: 600; - font-size: 20px; - opacity: .9; + color: #e0e0e0; + font-weight: 500; background: none; border: none; cursor: pointer; + position: relative; + padding: 0.25rem 0; } +.landing-nav a::after, +.landing-nav .link-button::after { + content: ""; + position: absolute; + left: 0; + bottom: -4px; + width: 0; + height: 2px; + background: var(--accent); + transition: width 0.3s ease; +} - -.landing-page .navlinks .nav-btn:hover { - background: var(--accent-900); - padding: 10px 18px; - border-radius: 10px; - +.landing-nav a:hover::after, +.landing-nav .link-button:hover::after { + width: 100%; } -.landing-page .signin { - padding: 10px 18px; - border-radius: 10px; - background: var(--accent); - color: #032224; +.btn-primary, +.btn-secondary { + border-radius: 999px; + padding: 0.75rem 1.75rem; font-weight: 600; + border: none; + cursor: pointer; + transition: all 0.3s ease; text-decoration: none; + display: inline-flex; + align-items: center; + justify-content: center; + white-space: nowrap; } -.landing-page .signin:hover { background: var(--accent-900); } - +.btn-primary { + color: var(--text-main); + background: linear-gradient(135deg, var(--accent), var(--accent-strong)); + box-shadow: 0 8px 30px rgba(30, 144, 255, 0.35); +} -.landing-page .hero { - margin-top: 70px; - min-height: 80vh; - display: flex; - align-items: center; - justify-content: flex-start; - padding-left: 40px; +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 12px 30px rgba(64, 224, 208, 0.5); } -.landing-page .frame { - max-width: 700px; +.btn-secondary { + border: 2px solid var(--accent); background: transparent; - box-shadow: none; - padding: 0; + color: var(--accent); } -.landing-page .kicker { - font-size: 14px; - color: #b6f3ee; - font-weight: 700; - letter-spacing: .3px; - text-transform: uppercase; +.btn-secondary:hover { + background: var(--accent); + color: var(--bg-dark); + transform: translateY(-2px); } -.landing-page h1 { - margin: 0; - font-size: 56px; - line-height: 1.1; - font-weight: 800; - text-shadow: 0 2px 18px rgba(0,0,0,.35); +/* Hero */ +.landing-hero { + background: var(--bg-gradient); + padding: 6rem 5% 4rem; + position: relative; + isolation: isolate; +} + +.landing-hero::before, +.landing-hero::after { + content: ""; + position: absolute; + border-radius: 50%; + opacity: 0.9; + z-index: -1; +} + +.landing-hero::before { + width: 600px; + height: 600px; + background: radial-gradient(circle, rgba(64, 224, 208, 0.15) 0%, transparent 70%); + top: -200px; + right: -200px; + animation: heroPulse 8s ease-in-out infinite; +} + +.landing-hero::after { + width: 420px; + height: 420px; + background: radial-gradient(circle, rgba(30, 144, 255, 0.2) 0%, transparent 70%); + bottom: -150px; + left: -100px; + animation: heroPulse 6s ease-in-out infinite; +} + +@keyframes heroPulse { + 0%, + 100% { + transform: scale(1); + opacity: 0.5; + } + 50% { + transform: scale(1.15); + opacity: 0.85; + } +} + +.hero-content { + max-width: 1200px; + margin: 0 auto; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 3.5rem; + align-items: center; +} + +.hero-text h1 { + font-size: clamp(2.5rem, 5vw, 3.5rem); + margin-bottom: 1.5rem; + line-height: 1.2; + background: linear-gradient(135deg, #ffffff, var(--accent)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; } -.landing-page .sub { - color: var(--muted); - font-size: 18px; +.hero-text p { + color: var(--text-muted); + font-size: 1.2rem; line-height: 1.6; - max-width: 700px; + margin-bottom: 2rem; } -.landing-page .cta { +.hero-buttons { display: flex; - gap: 14px; flex-wrap: wrap; - margin-top: 10px; + gap: 1rem; } -.landing-page .btn { - appearance: none; - border: 0; - cursor: pointer; - padding: 12px 18px; - border-radius: 10px; +.hero-visual { + display: flex; + flex-direction: column; + gap: 1.25rem; +} + +.floating-card { + background: rgba(255, 255, 255, 0.05); + border: 1px solid var(--border); + border-radius: 20px; + padding: 1.75rem; + backdrop-filter: blur(10px); + animation: float 6s ease-in-out infinite; +} + +.floating-card:nth-child(2) { + animation-delay: 0.8s; +} + +.floating-card:nth-child(3) { + animation-delay: 1.6s; +} + +@keyframes float { + 0%, + 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-18px); + } +} + +.card-icon { + width: 48px; + height: 48px; + border-radius: 14px; + background: linear-gradient(135deg, var(--accent), var(--accent-strong)); + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + margin-bottom: 1rem; +} + +.floating-card h3 { + margin-bottom: 0.5rem; + font-size: 1.2rem; +} + +.floating-card p { + margin: 0; + color: var(--text-muted); +} + +/* Stats */ +.landing-stats { + background: linear-gradient(135deg, #0a1628, #1e3a5f); + padding: 5rem 5%; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 2rem; + max-width: 1200px; + margin: 0 auto; +} + +.stat-card { + text-align: center; + padding: 2rem; + border-radius: 20px; + border: 1px solid var(--border); + background: rgba(255, 255, 255, 0.03); + transition: transform 0.3s ease, border 0.3s ease; +} + +.stat-card:hover { + transform: translateY(-6px); + border-color: var(--accent); +} + +.stat-number { + font-size: 3rem; font-weight: 700; - font-size: 15px; - letter-spacing: .2px; - text-decoration: none; + margin-bottom: 0.5rem; + background: linear-gradient(135deg, var(--accent), var(--accent-strong)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; } -.landing-page .btn-primary { - background: var(--accent); - color: #041317; - box-shadow: 0 6px 16px rgba(40,224,214,.35); +.stat-label { + color: var(--text-muted); + font-size: 1.1rem; } -.landing-page .btn-primary:hover { - transform: translateY(-1px); - filter: saturate(1.05); - background: var(--accent-900); +/* Features */ +.landing-features { + padding: 6rem 5%; + background: var(--bg-darker); } -.landing-page .btn-outline { - background: transparent; - color: var(--text); - border: 1px solid #3a556a; +.section-header { + text-align: center; + max-width: 720px; + margin: 0 auto 3rem; } -.landing-page .btn-outline:hover { - background: rgba(255,255,255,.06); +.section-header h2 { + color: var(--accent); + font-family: inherit; + text-transform: none; + letter-spacing: 0.5px; + font-size: clamp(1.5rem, 3vw, 2.2rem); + font-weight: 700; + margin-bottom: 0.5rem; } +.landing-features h3 { + font-size: clamp(2.25rem, 4vw, 2.8rem); + margin-bottom: 1rem; + color: var(--text-main); + font-family: inherit; + text-transform: none; + letter-spacing: 0.5px; +} -@media (min-width: 780px) { - .landing-page .brand-name { display: inline; } - .landing-page .hero h1 { font-size: 56px; } +.section-subtitle { + color: var(--text-muted); + line-height: 1.6; } -@media (max-width: 760px) { - .landing-page .navlinks .hide-sm { display: none; } - .landing-page .wrap { padding: 16px 14px 40px; } - .landing-page .hero { margin-top: 48px; padding-left: 20px; } - .landing-page .hero h1 { font-size: 36px; } +.section-header p { + margin: 0; } +.features-grid { + display: grid; + grid-template-columns: repeat(1, minmax(0, 1fr)); + gap: 1.5rem; + max-width: 1200px; + margin: 0 auto; +} -.sr-only{ position:absolute; left:-10000px; top:auto; width:1px; height:1px; overflow:hidden; } +.feature-card { + background: rgba(255, 255, 255, 0.03); + border: 1px solid var(--border); + border-radius: 20px; + padding: 2rem; + position: relative; + overflow: hidden; + transition: transform 0.3s ease, border 0.3s ease, box-shadow 0.3s ease; +} + +.feature-card::before { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient( + 135deg, + rgba(64, 224, 208, 0.15), + rgba(30, 144, 255, 0.12) + ); + opacity: 0; + transition: opacity 0.3s ease; +} -.features{ - padding: 80px 0 72px; +.feature-card:hover { + transform: translateY(-8px); + border-color: var(--accent); + box-shadow: 0 20px 45px rgba(30, 144, 255, 0.2); } -.feature-grid{ +.feature-card:hover::before { + opacity: 1; +} + +.feature-icon { + width: 60px; + height: 60px; + border-radius: 15px; + background: linear-gradient(135deg, var(--accent), var(--accent-strong)); + display: flex; + align-items: center; + justify-content: center; + font-size: 1.6rem; + margin-bottom: 1.25rem; + position: relative; + z-index: 1; +} + +.feature-card h3 { + font-size: 1.25rem; + margin-bottom: 1rem; + position: relative; + z-index: 1; +} + +.feature-card p { + color: var(--text-muted); + line-height: 1.6; + position: relative; + z-index: 1; +} + +/* Benefits */ +.landing-benefits { + padding: 6rem 5%; + background: var(--bg-darker); +} + +.benefits-container { + max-width: 1200px; + margin: 0 auto; display: grid; - grid-template-columns: repeat(12, 1fr); - gap: 18px; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 3rem; + align-items: center; } +.benefits-visual { + min-height: 320px; + border-radius: 24px; + border: 1px solid var(--border); + background: radial-gradient(circle at 20% 20%, rgba(64, 224, 208, 0.25), transparent 55%), + radial-gradient(circle at 80% 30%, rgba(30, 144, 255, 0.25), transparent 60%), + linear-gradient(135deg, #1e3a5f, #0a1628); + position: relative; + overflow: hidden; +} -.feature-card{ - grid-column: span 12; - background: - radial-gradient(120% 120% at 10% -10%, rgba(42,215,209,0.08) 0%, rgba(8,20,30,0) 60%), - var(--card); - border: 1px solid rgba(120,220,255,0.18); - border-radius: var(--radius); - padding: 20px; - color: var(--text); - box-shadow: var(--shadow); - backdrop-filter: blur(6px); - -webkit-backdrop-filter: blur(6px); - transition: transform .3s cubic-bezier(.2,.8,.2,1), border-color .3s, box-shadow .3s; - outline: none; +.benefits-network { + position: absolute; + inset: 0; + pointer-events: none; } -.feature-card:hover, -.feature-card:focus-visible{ - transform: translateY(-4px); - border-color: rgba(42,215,209,.40); - box-shadow: 0 16px 40px rgba(0,220,200,.10), var(--shadow); +.benefits-network .node { + position: absolute; + width: 16px; + height: 16px; + border-radius: 50%; + background: var(--accent); + box-shadow: 0 0 20px rgba(64, 224, 208, 0.8); } +.benefits-network .node-1 { top: 20%; left: 22%; } +.benefits-network .node-2 { top: 40%; left: 60%; background: var(--accent-strong); box-shadow: 0 0 20px rgba(30, 144, 255, 0.8); } +.benefits-network .node-3 { top: 65%; left: 35%; } +.benefits-network .node-4 { top: 30%; left: 80%; background: var(--accent-strong); } -.feature-icon{ - width: 44px; height: 44px; - display: grid; place-items: center; - border-radius: 12px; - margin-bottom: 12px; - color: #2ad7d1; - background: radial-gradient(120% 120% at 0% 0%, rgba(42,215,209,.25), rgba(42,215,209,0) 60%); - filter: drop-shadow(0 4px 16px rgba(42,215,209,.28)); +.benefits-network .link { + position: absolute; + height: 2px; + background: linear-gradient(90deg, var(--accent), transparent); + transform-origin: left center; + opacity: 0.6; } -.feature-title{ - font-size: 1.05rem; - font-weight: 800; - letter-spacing: .2px; - margin: 6px 0 6px; +.benefits-network .link-1 { + top: 28%; + left: 23%; + width: 45%; + transform: rotate(12deg); } +.benefits-network .link-2 { + top: 50%; + left: 36%; + width: 32%; + transform: rotate(-20deg); +} -.feature-grid{ - grid-template-columns: 1fr; - row-gap: 22px; +.benefits-network .link-3 { + top: 58%; + left: 60%; + width: 22%; + transform: rotate(25deg); + background: linear-gradient(90deg, var(--accent-strong), transparent); } -@media (min-width: 640px){ - .feature-grid{ grid-template-columns: repeat(2, 1fr); } - .feature-card{ grid-column: auto; } +.benefits-graphic { + position: absolute; + inset: 0; + background-image: linear-gradient( + rgba(255, 255, 255, 0.05) 1px, + transparent 1px + ), + linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px); + background-size: 40px 40px; + opacity: 0.6; } +.benefits-content h2 { + font-size: 2.5rem; + margin-bottom: 2rem; +} -@media (min-width: 1024px){ - .feature-grid{ grid-template-columns: repeat(2, 1fr); } - .feature-card{ grid-column: auto; } +.benefit-item { + display: flex; + gap: 1rem; + padding: 1.5rem; + border-radius: 16px; + border: 1px solid var(--border); + background: rgba(255, 255, 255, 0.02); + transition: transform 0.3s ease, border 0.3s ease; + align-items: flex-start; } -.insights { - padding: 60px 20px; - text-align: center; - max-width: 900px; - margin: 0 auto; +.benefit-item:hover { + border-color: var(--accent); + transform: translateX(8px); } -.insights-tagline { - font-size: 18px; - color: var(--text); - margin-bottom: 20px; - font-weight: 500; +.benefit-icon { + width: 50px; + height: 50px; + border-radius: 14px; + background: linear-gradient(135deg, var(--accent), var(--accent-strong)); + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + flex-shrink: 0; } -.why-title { - font-size: 22px; - font-weight: 600; - margin-bottom: 40px; +.benefit-text h3 { + margin: 0 0 0.35rem; } -.why-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 30px; +.benefit-text p { + color: var(--text-muted); + margin: 0; + line-height: 1.6; } -.why-card { - background: var(--card); - border: 1px solid rgba(120,220,255,0.18); - border-radius: var(--radius); - padding: 24px; - box-shadow: var(--shadow); +/* CTA */ +.landing-cta { + padding: 6rem 5%; + background: linear-gradient(135deg, #1e3a5f, #0a1628); text-align: center; - transition: transform .3s; } -.why-card:hover { - transform: translateY(-4px); +.cta-content { + max-width: 760px; + margin: 0 auto; +} + +.landing-cta h2 { + font-size: 2.75rem; + margin-bottom: 1rem; +} + +.landing-cta p { + color: var(--text-muted); + font-size: 1.2rem; + margin-bottom: 2rem; } -.why-icon { - margin-bottom: 12px; - color: #2ad7d1; +.cta-buttons { display: flex; justify-content: center; + gap: 1rem; + flex-wrap: wrap; } -.why-card h3 { - font-size: 16px; - font-weight: 700; - margin: 0 0 8px; +/* Footer */ +.landing-footer { + background: var(--bg-dark); + padding: 4rem 5% 2.5rem; + border-top: 1px solid var(--border); +} + +.footer-content { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 2rem; + max-width: 1200px; + margin: 0 auto 2rem; } -.why-card p { - font-size: 14px; - color: var(--muted); +.footer-section h3 { + margin-bottom: 1rem; + font-size: 1.1rem; +} + +.footer-section ul { + list-style: none; + padding: 0; margin: 0; } +.footer-section li + li { + margin-top: 0.75rem; +} + +.footer-section a { + color: var(--text-muted); + text-decoration: none; + transition: color 0.3s ease; +} + +.footer-section a:hover { + color: var(--accent); +} + +.footer-bottom { + text-align: center; + color: var(--text-muted); + border-top: 1px solid var(--border); + padding-top: 1.5rem; + font-size: 0.95rem; +} + +/* Responsive */ +@media (max-width: 768px) { + .landing-header { + flex-direction: column; + gap: 1rem; + } + .landing-nav { + flex-wrap: wrap; + justify-content: center; + } + .hero-text { + text-align: center; + } + .hero-buttons { + justify-content: center; + } + .hero-visual { + max-width: 420px; + margin: 0 auto; + } +} + +@media (min-width: 768px) { + .features-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (min-width: 1024px) { + .features-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} diff --git a/frontend/src/pages/Landing/LandingPage.js b/frontend/src/pages/Landing/LandingPage.js index 06e1e95c..42cc26c2 100644 --- a/frontend/src/pages/Landing/LandingPage.js +++ b/frontend/src/pages/Landing/LandingPage.js @@ -1,143 +1,30 @@ import React from "react"; import "./LandingPage.css"; - -const FeatureCard = ({ icon, title, desc }) => ( -
- -

{title}

-

{desc}

-
-); +import LandingHeader from "./components/LandingHeader"; +import HeroSection from "./components/HeroSection"; +import StatsSection from "./components/StatsSection"; +import FeaturesSection from "./components/FeaturesSection"; +import BenefitsSection from "./components/BenefitsSection"; +import CTASection from "./components/CTASection"; +import LandingFooter from "./components/LandingFooter"; const LandingPage = ({ onSignInClick, onAboutClick }) => { return (
-
-
-
-
- AutoAudit Logo -
-
- -
- -
-
-

- Access your compliance
- dashboard and
- security insights. -

-

- Compliance made easy for you. View your dashboards anytime, anywhere. -

-
- - Learn More -
-
-
- -
-

Key Features

- -
- - - - } - title="Microsoft 365 Integration" - desc="Seamlessly connect to your Microsoft 365 tenant using secure Graph API integration. Monitor MFA enforcement, audit logging, and conditional access policies in real-time." - /> - - - - - - - } - title="CIS Benchmark Compliance" - desc="Automatically assess your cloud configurations against CIS Microsoft 365 Foundations Benchmark. Get instant visibility into compliance gaps and security posture." - /> - - - - - } - title="Automated Scanning" - desc="Continuous monitoring of security settings, external sharing permissions, and policy configurations. Detect misconfigurations before they become security risks." - /> - - - - - } - title="Actionable Reports" - desc="Generate comprehensive compliance reports with risk assessments and remediation recommendations. Export audit-ready documentation for regulatory requirements." - /> -
-
- -
-

-

Actionable insights at a glance

-

- -

Why Choose AutoAudit?

- -
-
-
- - - -
-

Enterprise-Grade Security

-

Bank-level encrypted data handling

-
- -
-
- - - -
-

Fast & Automated

-

Reports in minutes, not days

-
- -
-
- - - - - -
-

Audit-Ready Reports

-

Align with regulatory frameworks

-
-
-
- -
+ +
+ + + + + +
+
); }; -export default LandingPage; \ No newline at end of file +export default LandingPage; diff --git a/frontend/src/pages/Landing/components/BenefitsSection.js b/frontend/src/pages/Landing/components/BenefitsSection.js new file mode 100644 index 00000000..a684d08e --- /dev/null +++ b/frontend/src/pages/Landing/components/BenefitsSection.js @@ -0,0 +1,64 @@ +import React from "react"; + +const benefits = [ + { + icon: "⏱️", + title: "Save Time & Resources", + description: + "Reduce manual compliance work by 80% so your team can focus on strategic initiatives.", + }, + { + icon: "🎯", + title: "Stay Ahead of Threats", + description: + "Proactive monitoring identifies vulnerabilities before they are exploited.", + }, + { + icon: "✅", + title: "Ensure Compliance", + description: + "Stay aligned with CIS, NIST, ISO 27001, SOC 2, and other regulatory frameworks.", + }, + { + icon: "💡", + title: "Expert Guidance", + description: + "Every finding includes prioritized remediation steps to improve security posture.", + }, +]; + +const BenefitsSection = () => { + return ( +
+
+ +
+ ); +}; + +export default BenefitsSection; diff --git a/frontend/src/pages/Landing/components/CTASection.js b/frontend/src/pages/Landing/components/CTASection.js new file mode 100644 index 00000000..91230413 --- /dev/null +++ b/frontend/src/pages/Landing/components/CTASection.js @@ -0,0 +1,25 @@ +import React from "react"; + +const CTASection = ({ onSignInClick }) => { + return ( +
+
+

Ready to transform your compliance process?

+

+ Join thousands of organizations that trust AutoAudit to keep their + Microsoft 365 environment secure and compliant. +

+
+ + + Schedule Demo + +
+
+
+ ); +}; + +export default CTASection; diff --git a/frontend/src/pages/Landing/components/FeaturesSection.js b/frontend/src/pages/Landing/components/FeaturesSection.js new file mode 100644 index 00000000..6ab78846 --- /dev/null +++ b/frontend/src/pages/Landing/components/FeaturesSection.js @@ -0,0 +1,71 @@ +import React from "react"; + +const features = [ + { + icon: "🔗", + title: "Microsoft 365 Integration", + description: + "Secure Graph API integration monitors MFA enforcement, audit logging, and conditional access policies in real-time.", + }, + { + icon: "📋", + title: "CIS Benchmark Compliance", + description: + "Automatically assess your cloud configurations against CIS Microsoft 365 benchmarks and surface posture gaps.", + }, + { + icon: "⚡", + title: "Automated Scanning", + description: + "Continuous monitoring of security settings, sharing permissions, and policies catches issues before they escalate.", + }, + { + icon: "📊", + title: "Actionable Reports", + description: + "Generate audit-ready compliance reports with risk assessments and remediation guidance in minutes.", + }, + { + icon: "🛡️", + title: "Enterprise-Grade Security", + description: + "Bank-level encrypted data handling with a zero-knowledge architecture keeps your sensitive data in your control.", + }, + { + icon: "🚀", + title: "Fast & Automated", + description: + "Automated workflows reduce manual checks and cut audit preparation time by 80%.", + }, +]; + +const FeatureCard = ({ icon, title, description }) => ( +
+
{icon}
+

{title}

+

{description}

+
+); + +const FeaturesSection = () => { + return ( +
+
+

Features

+

Everything you need for compliance

+

+ Comprehensive tools and insights to keep your organization secure and + audit-ready. +

+
+ +
+ {features.map((feature) => ( + + ))} +
+
+ ); +}; + +export default FeaturesSection; diff --git a/frontend/src/pages/Landing/components/HeroSection.js b/frontend/src/pages/Landing/components/HeroSection.js new file mode 100644 index 00000000..c350d165 --- /dev/null +++ b/frontend/src/pages/Landing/components/HeroSection.js @@ -0,0 +1,57 @@ +import React from "react"; + +const floatingCards = [ + { + icon: "🔒", + title: "99.9% Uptime", + subtitle: "Enterprise-grade reliability you can trust", + }, + { + icon: "⚡", + title: "Real-Time Monitoring", + subtitle: "Instant alerts and comprehensive insights", + }, + { + icon: "📊", + title: "Actionable Reports", + subtitle: "Export audit-ready documentation instantly", + }, +]; + +const HeroSection = ({ onSignInClick }) => { + return ( +
+
+
+

AutoAudit Platform

+

Access your compliance dashboard and security insights.

+

+ Compliance made easy for you. View your dashboards anytime, + anywhere. Automate security monitoring and stay ahead of threats + with real-time insights. +

+
+ + + Learn More + +
+
+ + +
+
+ ); +}; + +export default HeroSection; diff --git a/frontend/src/pages/Landing/components/LandingFooter.js b/frontend/src/pages/Landing/components/LandingFooter.js new file mode 100644 index 00000000..aa57ec24 --- /dev/null +++ b/frontend/src/pages/Landing/components/LandingFooter.js @@ -0,0 +1,46 @@ +import React from "react"; + +const footerColumns = [ + { + title: "Product", + links: ["Features", "Pricing", "Integrations", "Security"], + }, + { + title: "Resources", + links: ["Documentation", "API Reference", "Blog", "Case Studies"], + }, + { + title: "Company", + links: ["About Us", "Careers", "Contact", "Partners"], + }, + { + title: "Legal", + links: ["Privacy Policy", "Terms of Service", "Cookie Policy", "Compliance"], + }, +]; + +const LandingFooter = () => { + return ( + + ); +}; + +export default LandingFooter; diff --git a/frontend/src/pages/Landing/components/LandingHeader.js b/frontend/src/pages/Landing/components/LandingHeader.js new file mode 100644 index 00000000..6a6ac787 --- /dev/null +++ b/frontend/src/pages/Landing/components/LandingHeader.js @@ -0,0 +1,24 @@ +import React from "react"; + +const LandingHeader = ({ onSignInClick, onAboutClick }) => { + return ( +
+ + AutoAudit + + + +
+ ); +}; + +export default LandingHeader; diff --git a/frontend/src/pages/Landing/components/StatsSection.js b/frontend/src/pages/Landing/components/StatsSection.js new file mode 100644 index 00000000..79316913 --- /dev/null +++ b/frontend/src/pages/Landing/components/StatsSection.js @@ -0,0 +1,25 @@ +import React from "react"; + +const stats = [ + { value: "10K+", label: "Organizations Trust Us" }, + { value: "99.9%", label: "Compliance Rate" }, + { value: "24/7", label: "Automated Monitoring" }, + { value: "500M+", label: "Security Events Analyzed" }, +]; + +const StatsSection = () => { + return ( +
+
+ {stats.map((stat) => ( +
+
{stat.value}
+

{stat.label}

+
+ ))} +
+
+ ); +}; + +export default StatsSection; From 3d6ba46600a094f932bca3b3ba7962940aa24178 Mon Sep 17 00:00:00 2001 From: Nikhil Jadav Date: Fri, 21 Nov 2025 13:40:45 +1100 Subject: [PATCH 002/111] about us page redesigned --- frontend/src/App.js | 8 +- frontend/src/pages/Landing/AboutUs.css | 105 +++++++++++------- frontend/src/pages/Landing/AboutUs.js | 81 ++++++-------- .../Landing/components/FeaturesSection.js | 42 +------ .../pages/Landing/components/LandingHeader.js | 28 ++++- frontend/src/pages/Landing/featuresData.js | 38 +++++++ 6 files changed, 171 insertions(+), 131 deletions(-) create mode 100644 frontend/src/pages/Landing/featuresData.js diff --git a/frontend/src/App.js b/frontend/src/App.js index aba1b3b0..f5818dc3 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -105,7 +105,11 @@ function App() { navigate('/')} /> + navigate('/')} + onSignInClick={() => navigate('/login')} + onAboutClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} + /> } /> @@ -182,4 +186,4 @@ function App() { ); } -export default App; \ No newline at end of file +export default App; diff --git a/frontend/src/pages/Landing/AboutUs.css b/frontend/src/pages/Landing/AboutUs.css index 127ad63f..252a8225 100644 --- a/frontend/src/pages/Landing/AboutUs.css +++ b/frontend/src/pages/Landing/AboutUs.css @@ -8,33 +8,6 @@ font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; } -.about-nav { - padding: 20px 40px; - position: sticky; - top: 0; - background: rgba(13, 27, 42, 0.95); - backdrop-filter: blur(10px); - border-bottom: 1px solid rgba(120, 220, 255, 0.18); - z-index: 100; -} - -.back-btn { - background: transparent; - color: #28e0d6; - border: 1px solid #28e0d6; - padding: 10px 20px; - border-radius: 8px; - cursor: pointer; - font-weight: 600; - font-size: 14px; - transition: all 0.3s ease; -} - -.back-btn:hover { - background: rgba(40, 224, 214, 0.1); - transform: translateY(-1px); -} - .about-content { max-width: 1000px; margin: 0 auto; @@ -47,16 +20,16 @@ } .about-logo { - width: 200px; + width: 180px; height: auto; - margin-bottom: 30px; border-radius: 15px; - margin-left: 350px; box-shadow: 0 0 20px rgba(40, 224, 214, 0.3); + display: block; + margin: 0 auto 30px; } -.about-hero h1 { - font-size: 48px; +.about-hero h2 { + font-size: 42px; font-weight: 800; margin: 0 0 16px 0; background: linear-gradient(135deg, #28e0d6, #1fc9c0); @@ -94,6 +67,12 @@ color: #28e0d6; } +.about-section#mission h2, +.about-section#features h2, +.about-section#standards h2 { + text-align: center; +} + .about-section p { font-size: 16px; line-height: 1.7; @@ -103,7 +82,7 @@ .features-overview { display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 30px; margin-top: 30px; } @@ -139,9 +118,26 @@ margin: 0; } +.about-feature-grid { + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); +} + +.about-feature-item { + background: rgba(13, 27, 42, 0.5); + padding: 20px; + border-radius: 14px; + border: 1px solid rgba(42, 215, 209, 0.2); + transition: transform 0.3s ease, border-color 0.3s ease; +} + +.about-feature-item:hover { + transform: translateY(-4px); + border-color: rgba(42, 215, 209, 0.5); +} + .standards-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; margin-top: 30px; } @@ -172,6 +168,26 @@ margin: 0; } +.about-why { + text-align: center; +} + +.about-why p { + max-width: 720px; + margin-left: auto; + margin-right: auto; +} + +.about-commitment { + text-align: center; +} + +.about-commitment p { + max-width: 760px; + margin-left: auto; + margin-right: auto; +} + .about-cta { text-align: center; padding: 60px 40px; @@ -216,15 +232,22 @@ } @media (max-width: 768px) { + .about-header { + flex-direction: column; + gap: 1.5rem; + padding: 18px 20px; + } + + .about-nav-links { + flex-wrap: wrap; + justify-content: center; + } + .about-content { padding: 0 20px 60px; } - .about-nav { - padding: 15px 20px; - } - - .about-hero h1 { + .about-hero h2 { font-size: 36px; } @@ -247,7 +270,7 @@ } .standards-grid { - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + grid-template-columns: 1fr; } .about-cta { @@ -257,4 +280,4 @@ .about-cta h2 { font-size: 26px; } -} \ No newline at end of file +} diff --git a/frontend/src/pages/Landing/AboutUs.js b/frontend/src/pages/Landing/AboutUs.js index 9c6a1037..553f824b 100644 --- a/frontend/src/pages/Landing/AboutUs.js +++ b/frontend/src/pages/Landing/AboutUs.js @@ -1,21 +1,30 @@ -import React from 'react'; -import './AboutUs.css'; +import React from "react"; +import "./AboutUs.css"; +import "./LandingPage.css"; +import { landingFeatures } from "./featuresData"; +import LandingFooter from "./components/LandingFooter"; +import LandingHeader from "./components/LandingHeader"; -const AboutUs = ({ onBack }) => { +const AboutUs = ({ onBack, onSignInClick, onAboutClick }) => { return (
- +
AutoAudit Logo -

About AutoAudit

-

Revolutionizing Cloud Compliance for Modern Enterprises

+

About AutoAudit

+

+ Revolutionizing Cloud Compliance for Modern Enterprises +

-
+

Our Mission

AutoAudit empowers organizations to maintain robust security postures in their Microsoft 365 environments @@ -24,42 +33,24 @@ const AboutUs = ({ onBack }) => {

-
+

What We Do

-
-
-
- - - -
-

Automated Compliance Scanning

-

Continuously monitor your Microsoft 365 environment against CIS benchmarks and industry standards

-
-
-
- - - - - -
-

Risk Assessment & Reporting

-

Generate comprehensive reports with actionable insights and remediation recommendations

-
-
-
- - - -
-

Real-time Monitoring

-

Stay ahead of security misconfigurations with continuous monitoring and instant alerts

-
+
+ {landingFeatures.map((feature) => ( +
+ +
+

{feature.title}

+

{feature.description}

+
+
+ ))}
-
+

Why AutoAudit?

In today's rapidly evolving threat landscape, manual compliance checks are no longer sufficient. @@ -73,7 +64,7 @@ const AboutUs = ({ onBack }) => {

-
+

Industry Standards We Support

@@ -95,7 +86,7 @@ const AboutUs = ({ onBack }) => {
-
+

Our Commitment

We're committed to providing enterprise-grade security tools that are both powerful and accessible. @@ -117,8 +108,10 @@ const AboutUs = ({ onBack }) => {

+ +
); }; -export default AboutUs; \ No newline at end of file +export default AboutUs; diff --git a/frontend/src/pages/Landing/components/FeaturesSection.js b/frontend/src/pages/Landing/components/FeaturesSection.js index 6ab78846..ca639100 100644 --- a/frontend/src/pages/Landing/components/FeaturesSection.js +++ b/frontend/src/pages/Landing/components/FeaturesSection.js @@ -1,43 +1,5 @@ import React from "react"; - -const features = [ - { - icon: "🔗", - title: "Microsoft 365 Integration", - description: - "Secure Graph API integration monitors MFA enforcement, audit logging, and conditional access policies in real-time.", - }, - { - icon: "📋", - title: "CIS Benchmark Compliance", - description: - "Automatically assess your cloud configurations against CIS Microsoft 365 benchmarks and surface posture gaps.", - }, - { - icon: "⚡", - title: "Automated Scanning", - description: - "Continuous monitoring of security settings, sharing permissions, and policies catches issues before they escalate.", - }, - { - icon: "📊", - title: "Actionable Reports", - description: - "Generate audit-ready compliance reports with risk assessments and remediation guidance in minutes.", - }, - { - icon: "🛡️", - title: "Enterprise-Grade Security", - description: - "Bank-level encrypted data handling with a zero-knowledge architecture keeps your sensitive data in your control.", - }, - { - icon: "🚀", - title: "Fast & Automated", - description: - "Automated workflows reduce manual checks and cut audit preparation time by 80%.", - }, -]; +import { landingFeatures } from "../featuresData"; const FeatureCard = ({ icon, title, description }) => (
@@ -60,7 +22,7 @@ const FeaturesSection = () => {
- {features.map((feature) => ( + {landingFeatures.map((feature) => ( ))}
diff --git a/frontend/src/pages/Landing/components/LandingHeader.js b/frontend/src/pages/Landing/components/LandingHeader.js index 6a6ac787..d1ebe9b6 100644 --- a/frontend/src/pages/Landing/components/LandingHeader.js +++ b/frontend/src/pages/Landing/components/LandingHeader.js @@ -1,6 +1,11 @@ import React from "react"; -const LandingHeader = ({ onSignInClick, onAboutClick }) => { +const LandingHeader = ({ + onSignInClick, + onAboutClick, + onHomeClick, + showSignIn = true, +}) => { return (
@@ -8,14 +13,29 @@ const LandingHeader = ({ onSignInClick, onAboutClick }) => {
); diff --git a/frontend/src/pages/Landing/featuresData.js b/frontend/src/pages/Landing/featuresData.js new file mode 100644 index 00000000..d783ca9f --- /dev/null +++ b/frontend/src/pages/Landing/featuresData.js @@ -0,0 +1,38 @@ +export const landingFeatures = [ + { + icon: "🔗", + title: "Microsoft 365 Integration", + description: + "Secure Graph API integration monitors MFA enforcement, audit logging, and conditional access policies in real-time.", + }, + { + icon: "📋", + title: "CIS Benchmark Compliance", + description: + "Automatically assess your cloud configurations against CIS Microsoft 365 benchmarks and surface posture gaps.", + }, + { + icon: "⚡", + title: "Automated Scanning", + description: + "Continuous monitoring of security settings, sharing permissions, and policies catches issues before they escalate.", + }, + { + icon: "📊", + title: "Actionable Reports", + description: + "Generate audit-ready compliance reports with risk assessments and remediation guidance in minutes.", + }, + { + icon: "🛡️", + title: "Enterprise-Grade Security", + description: + "Bank-level encrypted data handling with a zero-knowledge architecture keeps your sensitive data in your control.", + }, + { + icon: "🚀", + title: "Fast & Automated", + description: + "Automated workflows reduce manual checks and cut audit preparation time by 80%.", + }, +]; From 27c214d5a51c49362aecd73ac9dbfab569a4c2a5 Mon Sep 17 00:00:00 2001 From: Nikhil Jadav Date: Fri, 21 Nov 2025 18:09:35 +1100 Subject: [PATCH 003/111] contact us page created --- frontend/contact_us_page.html | 803 ++++++++++++++++++ frontend/src/App.js | 15 + frontend/src/pages/Contact/ContactPage.css | 363 ++++++++ frontend/src/pages/Contact/ContactPage.js | 55 ++ .../pages/Contact/components/ContactForm.js | 131 +++ .../Contact/components/ContactInfoGrid.js | 83 ++ .../pages/Contact/components/FAQSection.js | 74 ++ frontend/src/pages/Landing/AboutUs.js | 3 +- frontend/src/pages/Landing/LandingPage.js | 3 +- .../pages/Landing/components/LandingFooter.js | 32 +- .../pages/Landing/components/LandingHeader.js | 10 + 11 files changed, 1564 insertions(+), 8 deletions(-) create mode 100644 frontend/contact_us_page.html create mode 100644 frontend/src/pages/Contact/ContactPage.css create mode 100644 frontend/src/pages/Contact/ContactPage.js create mode 100644 frontend/src/pages/Contact/components/ContactForm.js create mode 100644 frontend/src/pages/Contact/components/ContactInfoGrid.js create mode 100644 frontend/src/pages/Contact/components/FAQSection.js diff --git a/frontend/contact_us_page.html b/frontend/contact_us_page.html new file mode 100644 index 00000000..8776bdbb --- /dev/null +++ b/frontend/contact_us_page.html @@ -0,0 +1,803 @@ + + + + + + Contact Us - AutoAudit + + + + +
+ + +
+ + +
+
+

Get in Touch

+

Have questions about AutoAudit? Our team is here to help. Reach out and we'll respond as soon as possible.

+
+
+ + +
+
+ +
+
+
📧
+

Email Us

+

Our team typically responds within 24 hours

+ support@autoaudit.com
+ sales@autoaudit.com +
+ +
+
📞
+

Call Us

+

Monday - Friday, 9am - 6pm EST

+ +1 (234) 567-890
+ +1 (234) 567-891 +
+ +
+
📍
+

Visit Us

+

123 Security Boulevard
+ Suite 100
+ San Francisco, CA 94102
+ United States

+
+ +
+
🌐
+

Follow Us

+

Stay connected on social media

+ +
+
+ + +
+

Send us a Message

+
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + +
+ ✓ Thank you! Your message has been sent successfully. We'll get back to you soon. +
+
+
+
+
+ + +
+
+
+

Frequently Asked Questions

+

Quick answers to common questions about AutoAudit

+
+ +
+
+ How quickly can I get started with AutoAudit? + + +
+
+
+ You can be up and running in minutes. Simply sign up, connect your Microsoft 365 tenant using our secure OAuth integration, and start your first compliance scan immediately. No installation or complex setup required. +
+
+
+ +
+
+ What compliance frameworks does AutoAudit support? + + +
+
+
+ AutoAudit supports CIS Microsoft 365 Foundations Benchmark, NIST Cybersecurity Framework, ISO 27001, SOC 2, and GDPR compliance requirements. We continuously update our benchmarks to align with the latest security standards. +
+
+
+ +
+
+ Is my data secure with AutoAudit? + + +
+
+
+ Absolutely. We use bank-level encryption, zero-knowledge architecture, and follow strict security protocols. Your data is encrypted in transit and at rest. We're SOC 2 Type II certified and undergo regular third-party security audits. +
+
+
+ +
+
+ Do you offer a free trial? + + +
+
+
+ Yes! We offer a 14-day free trial with full access to all features. No credit card required. Experience the power of automated compliance monitoring risk-free. +
+
+
+ +
+
+ What kind of support do you provide? + + +
+
+
+ We provide email and chat support for all customers. Premium and Enterprise plans include priority support, dedicated account managers, and 24/7 emergency assistance. We also offer comprehensive documentation and video tutorials. +
+
+
+ +
+
+ Can I export compliance reports? + + +
+
+
+ Yes! Generate and export comprehensive compliance reports in PDF, Excel, or CSV formats. Reports are audit-ready and can be customized to meet your specific regulatory requirements. +
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/frontend/src/App.js b/frontend/src/App.js index f5818dc3..7ddbec03 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -10,6 +10,7 @@ import StyleGuide from './pages/StyleGuide'; // Authentication & Landing Components import LandingPage from './pages/Landing/LandingPage'; import AboutUs from './pages/Landing/AboutUs'; +import ContactPage from './pages/Contact/ContactPage'; import LoginPage from './pages/Auth/LoginPage'; import SignUpPage from './pages/Auth/SignUpPage'; @@ -98,6 +99,7 @@ function App() { navigate('/login')} onAboutClick={() => navigate('/about')} + onContactClick={() => navigate('/contact')} /> } /> @@ -109,10 +111,22 @@ function App() { onBack={() => navigate('/')} onSignInClick={() => navigate('/login')} onAboutClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} + onContactClick={() => navigate('/contact')} /> } /> + navigate('/')} + onNavigateAbout={() => navigate('/about')} + onSignIn={() => navigate('/login')} + /> + } + /> + navigate('/login')} onAboutClick={() => navigate('/about')} + onContactClick={() => navigate('/contact')} /> } /> diff --git a/frontend/src/pages/Contact/ContactPage.css b/frontend/src/pages/Contact/ContactPage.css new file mode 100644 index 00000000..f53fcb2b --- /dev/null +++ b/frontend/src/pages/Contact/ContactPage.css @@ -0,0 +1,363 @@ +.contact-page { + background: #0a1628; + color: #ffffff; + min-height: 100vh; + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +.contact-page main { + display: flex; + flex-direction: column; + gap: 0; +} + +.contact-hero { + padding: 10rem 5% 4rem; + background: linear-gradient(135deg, #0a1628 0%, #162a4a 50%, #1e3a5f 100%); + position: relative; + overflow: hidden; + text-align: center; +} + +.contact-hero::before, +.contact-hero::after { + content: ""; + position: absolute; + border-radius: 50%; + opacity: 0.9; + animation: pulse 8s ease-in-out infinite; +} + +.contact-hero::before { + width: 600px; + height: 600px; + background: radial-gradient(circle, rgba(64, 224, 208, 0.1) 0%, transparent 70%); + top: -200px; + right: -200px; +} + +.contact-hero::after { + width: 400px; + height: 400px; + background: radial-gradient(circle, rgba(30, 144, 255, 0.1) 0%, transparent 70%); + bottom: -100px; + left: -100px; + animation-duration: 6s; +} + +@keyframes pulse { + 0%, + 100% { + transform: scale(1); + opacity: 0.5; + } + 50% { + transform: scale(1.2); + opacity: 0.8; + } +} + +.contact-hero-content { + max-width: 800px; + margin: 0 auto; + position: relative; + z-index: 1; +} + +.contact-hero h1 { + font-size: clamp(2.5rem, 6vw, 3.5rem); + margin-bottom: 1rem; + background: linear-gradient(135deg, #ffffff, #40e0d0); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.contact-hero p { + color: #b0c4de; + font-size: 1.2rem; + line-height: 1.6; +} + +.contact-section { + padding: 6rem 5%; + background: #0f1f38; +} + +.contact-container { + max-width: 1200px; + margin: 0 auto; + display: grid; + grid-template-columns: 1fr 1.4fr; + gap: 3rem; +} + +.contact-info { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.info-card { + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(64, 224, 208, 0.1); + border-radius: 20px; + padding: 2rem; + transition: all 0.3s ease; +} + +.info-card:hover { + border-color: #40e0d0; + background: rgba(255, 255, 255, 0.05); + transform: translateY(-5px); +} + +.info-icon { + width: 56px; + height: 56px; + border-radius: 14px; + background: linear-gradient(135deg, #40e0d0, #1e90ff); + display: flex; + align-items: center; + justify-content: center; + font-size: 1.8rem; + margin-bottom: 1.25rem; +} + +.info-card h3 { + font-size: 1.25rem; + margin-bottom: 0.5rem; +} + +.info-card p { + color: #b0c4de; + line-height: 1.6; + margin-bottom: 0.5rem; + white-space: pre-line; +} + +.contact-link { + display: block; + color: #40e0d0; + text-decoration: none; + margin-top: 0.25rem; +} + +.contact-link:hover { + color: #1e90ff; +} + +.social-links { + display: flex; + gap: 1rem; + margin-top: 1rem; +} + +.social-link { + width: 44px; + height: 44px; + border-radius: 12px; + border: 1px solid rgba(64, 224, 208, 0.2); + display: flex; + align-items: center; + justify-content: center; + text-decoration: none; + color: #ffffff; + background: rgba(64, 224, 208, 0.12); + transition: all 0.3s ease; +} + +.social-link:hover { + background: linear-gradient(135deg, #40e0d0, #1e90ff); + border-color: transparent; + transform: translateY(-3px); +} + +.social-link svg { + width: 18px; + height: 18px; + display: block; +} + +.contact-form-wrapper { + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(64, 224, 208, 0.1); + border-radius: 20px; + padding: 3rem; + backdrop-filter: blur(10px); +} + +.contact-form-wrapper h2 { + margin-bottom: 1.5rem; +} + +.form-group { + margin-bottom: 1.5rem; +} + +.form-group label { + display: block; + margin-bottom: 0.5rem; + color: #b0c4de; +} + +.form-group input, +.form-group textarea, +.form-group select { + width: 100%; + padding: 1rem; + border-radius: 12px; + border: 1px solid rgba(64, 224, 208, 0.2); + background: rgba(255, 255, 255, 0.05); + color: #ffffff; + font-size: 1rem; + font-family: inherit; +} + +.form-group input:focus, +.form-group textarea:focus, +.form-group select:focus { + outline: none; + border-color: #40e0d0; + background: rgba(255, 255, 255, 0.08); + box-shadow: 0 0 0 2px rgba(64, 224, 208, 0.15); +} + +.form-row { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1.25rem; +} + +.form-group textarea { + min-height: 150px; + resize: vertical; +} + +.submit-btn { + width: 100%; + margin-top: 0.5rem; + font-size: 1.1rem; +} + +.success-message { + display: none; + margin-top: 1rem; + padding: 1rem; + border-radius: 12px; + border: 1px solid #40e0d0; + background: rgba(64, 224, 208, 0.12); + text-align: center; + color: #40e0d0; +} + +.success-message.show { + display: block; + animation: slideIn 0.4s ease; +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.faq-section { + padding: 6rem 5%; + background: linear-gradient(135deg, #0a1628, #1e3a5f); +} + +.faq-container { + max-width: 900px; + margin: 0 auto; +} + +.faq-header { + text-align: center; + margin-bottom: 2.5rem; +} + +.faq-header h2 { + font-size: 2.4rem; + margin-bottom: 0.75rem; +} + +.faq-header p { + color: #b0c4de; +} + +.faq-item { + border: 1px solid rgba(64, 224, 208, 0.1); + border-radius: 15px; + margin-bottom: 1rem; + background: rgba(255, 255, 255, 0.02); + overflow: hidden; +} + +.faq-question { + width: 100%; + text-align: left; + background: transparent; + border: none; + padding: 1.25rem 1.5rem; + color: #ffffff; + font-size: 1rem; + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; +} + +.faq-question:hover { + background: rgba(255, 255, 255, 0.04); +} + +.faq-icon { + font-size: 1.4rem; + color: #40e0d0; + transition: transform 0.3s ease; +} + +.faq-item.active .faq-icon { + transform: rotate(45deg); +} + +.faq-answer { + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease; +} + +.faq-item.active .faq-answer { + max-height: 500px; +} + +.faq-answer-content { + padding: 0 1.5rem 1.5rem; + color: #b0c4de; + line-height: 1.6; +} + +@media (max-width: 1024px) { + .contact-container { + grid-template-columns: 1fr; + } +} + +@media (max-width: 768px) { + .contact-hero { + padding-top: 8rem; + } + + .form-row { + grid-template-columns: 1fr; + } + + .contact-form-wrapper { + padding: 2rem; + } +} diff --git a/frontend/src/pages/Contact/ContactPage.js b/frontend/src/pages/Contact/ContactPage.js new file mode 100644 index 00000000..44332e83 --- /dev/null +++ b/frontend/src/pages/Contact/ContactPage.js @@ -0,0 +1,55 @@ +import React, { useState } from "react"; +import "./ContactPage.css"; +import "../Landing/LandingPage.css"; +import LandingHeader from "../Landing/components/LandingHeader"; +import LandingFooter from "../Landing/components/LandingFooter"; +import ContactInfoGrid from "./components/ContactInfoGrid"; +import ContactForm from "./components/ContactForm"; +import FAQSection from "./components/FAQSection"; + +const ContactHero = () => ( +
+
+

Contact AutoAudit

+

Get in Touch

+

+ Have questions about AutoAudit? Our team is here to help. Reach out and + we'll respond as soon as possible. +

+
+
+); + +const ContactPage = ({ onNavigateHome, onNavigateAbout, onSignIn }) => { + const [submitted, setSubmitted] = useState(false); + + const handleFormSuccess = () => { + setSubmitted(true); + setTimeout(() => setSubmitted(false), 5000); + }; + + return ( +
+ +
+ + +
+
+ + +
+
+ + +
+ +
+ ); +}; + +export default ContactPage; diff --git a/frontend/src/pages/Contact/components/ContactForm.js b/frontend/src/pages/Contact/components/ContactForm.js new file mode 100644 index 00000000..f79d8e3e --- /dev/null +++ b/frontend/src/pages/Contact/components/ContactForm.js @@ -0,0 +1,131 @@ +import React, { useState } from "react"; + +const initialState = { + firstName: "", + lastName: "", + email: "", + phone: "", + company: "", + subject: "", + message: "", +}; + +const ContactForm = ({ submitted, onSuccess }) => { + const [formData, setFormData] = useState(initialState); + + const handleChange = (event) => { + const { name, value } = event.target; + setFormData((prev) => ({ ...prev, [name]: value })); + }; + + const handleSubmit = (event) => { + event.preventDefault(); + onSuccess(); + setFormData(initialState); + }; + + return ( +
+

Send us a Message

+
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + -
- - - -
- ✓ Thank you! Your message has been sent successfully. We'll get back to you soon. -
-
-
- - - - -
-
-
-

Frequently Asked Questions

-

Quick answers to common questions about AutoAudit

-
- -
-
- How quickly can I get started with AutoAudit? - + -
-
-
- You can be up and running in minutes. Simply sign up, connect your Microsoft 365 tenant using our secure OAuth integration, and start your first compliance scan immediately. No installation or complex setup required. -
-
-
- -
-
- What compliance frameworks does AutoAudit support? - + -
-
-
- AutoAudit supports CIS Microsoft 365 Foundations Benchmark, NIST Cybersecurity Framework, ISO 27001, SOC 2, and GDPR compliance requirements. We continuously update our benchmarks to align with the latest security standards. -
-
-
- -
-
- Is my data secure with AutoAudit? - + -
-
-
- Absolutely. We use bank-level encryption, zero-knowledge architecture, and follow strict security protocols. Your data is encrypted in transit and at rest. We're SOC 2 Type II certified and undergo regular third-party security audits. -
-
-
- -
-
- Do you offer a free trial? - + -
-
-
- Yes! We offer a 14-day free trial with full access to all features. No credit card required. Experience the power of automated compliance monitoring risk-free. -
-
-
- -
-
- What kind of support do you provide? - + -
-
-
- We provide email and chat support for all customers. Premium and Enterprise plans include priority support, dedicated account managers, and 24/7 emergency assistance. We also offer comprehensive documentation and video tutorials. -
-
-
- -
-
- Can I export compliance reports? - + -
-
-
- Yes! Generate and export comprehensive compliance reports in PDF, Excel, or CSV formats. Reports are audit-ready and can be customized to meet your specific regulatory requirements. -
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/frontend/src/pages/Landing/LandingPage.css b/frontend/src/pages/Landing/LandingPage.css index 1710b7be..ee571c8c 100644 --- a/frontend/src/pages/Landing/LandingPage.css +++ b/frontend/src/pages/Landing/LandingPage.css @@ -11,6 +11,7 @@ color: var(--text-main); background: var(--bg-dark); min-height: 100vh; + overflow-x: hidden; } .landing-page * { From 59469e30b91033ed110225675a01b9b9483b999d Mon Sep 17 00:00:00 2001 From: Nikhil Jadav Date: Tue, 2 Dec 2025 18:44:43 +1100 Subject: [PATCH 005/111] login page added with pending changes --- frontend/login_page_redesign.html | 788 ++++++++++++++++++ frontend/src/pages/Auth/LoginPage.css | 597 +++++++++---- frontend/src/pages/Auth/LoginPage.js | 128 +-- .../src/pages/Auth/components/BrandPanel.js | 43 + .../src/pages/Auth/components/LoginHeader.js | 29 + .../src/pages/Auth/components/SignInPanel.js | 136 +++ 6 files changed, 1437 insertions(+), 284 deletions(-) create mode 100644 frontend/login_page_redesign.html create mode 100644 frontend/src/pages/Auth/components/BrandPanel.js create mode 100644 frontend/src/pages/Auth/components/LoginHeader.js create mode 100644 frontend/src/pages/Auth/components/SignInPanel.js diff --git a/frontend/login_page_redesign.html b/frontend/login_page_redesign.html new file mode 100644 index 00000000..4a5172bd --- /dev/null +++ b/frontend/login_page_redesign.html @@ -0,0 +1,788 @@ + + + + + + Sign In - AutoAudit + + + + +
+ + +
+ + +
+ +
+
+
+
+
+
+
+ +
+ + +
+

Compliance Made Simple

+

Access your Microsoft 365 compliance dashboard and security insights. Monitor, analyze, and improve your security posture in real-time.

+
+ +
+
+
🔒
+ Enterprise-grade security & encryption +
+
+
+ Real-time compliance monitoring +
+
+
📊
+ Instant audit-ready reports +
+
+
+
+ + +
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/frontend/src/pages/Auth/LoginPage.css b/frontend/src/pages/Auth/LoginPage.css index f1074a1e..7cf47000 100644 --- a/frontend/src/pages/Auth/LoginPage.css +++ b/frontend/src/pages/Auth/LoginPage.css @@ -1,297 +1,544 @@ -.login-container { - display: flex; +.login-page { min-height: 100vh; - background: #0f172a; + background: #0a1628; + color: #ffffff; + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + display: flex; + flex-direction: column; + overflow-x: hidden; +} + +.auth-header { + padding: 1.5rem 5%; + display: flex; + justify-content: space-between; + align-items: center; + background: rgba(10, 22, 40, 0.95); + border-bottom: 1px solid rgba(64, 224, 208, 0.1); + backdrop-filter: blur(10px); } +.auth-logo img { + height: 50px; + width: auto; + transition: transform 0.3s ease; + display: block; +} -.top-nav { - position: absolute; - top: 20px; - right: 30px; - z-index: 10; +.auth-logo img:hover { + transform: scale(1.04); } +.auth-nav { + display: flex; + gap: 1.5rem; + align-items: center; +} -.top-nav .home-link { - display: inline-block; - background: transparent; - color: #fdffff; - font-weight: 600; - font-size: 16px; +.auth-nav a { + color: #e0e0e0; text-decoration: none; - padding: 12px 16px; - border-radius: 8px; - transition: background 0.15s ease, color 0.15s ease, transform 0.15s ease; - border: 1px solid transparent; + font-weight: 500; + position: relative; } +.auth-nav a::after { + content: ""; + position: absolute; + left: 0; + bottom: -4px; + width: 0; + height: 2px; + background: #40e0d0; + transition: width 0.3s ease; +} -.top-nav .home-link:hover, -.top-nav .home-link:focus-visible { - background: #36dad6; - color: #ffffff; - border-color: #36dad6; - transform: translateY(-1px); - text-decoration: none; +.auth-nav a:hover { + color: #40e0d0; } +.auth-nav a:hover::after { + width: 100%; +} +.login-main { + flex: 1; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + min-height: calc(100vh - 120px); + gap: 1.5rem; + padding: 1.5rem 5% 2.5rem; +} -.login-left { - width: 40%; +.login-brand { + background: linear-gradient(135deg, #0a1628 0%, #162a4a 50%, #1e3a5f 100%); + position: relative; + padding: 2.5rem 3rem; display: flex; align-items: center; justify-content: center; - padding: 40px; + overflow: hidden; + border-radius: 24px; } -.login-content { - width: 100%; - max-width: 400px; +.login-brand::before, +.login-brand::after { + content: ""; + position: absolute; + border-radius: 50%; + opacity: 0.6; + animation: brandPulse 8s ease-in-out infinite; } -.logo-img { - width: 200px; - height: auto; - margin-bottom: 16px; - border-radius: 15px; - margin-left: 5px; - box-shadow: 1px 2px 20px 2px #0ff; +.login-brand::before { + width: 480px; + height: 480px; + background: radial-gradient(circle, rgba(64, 224, 208, 0.15) 0%, transparent 70%); + top: -160px; + right: -160px; } -.logo-section { - text-align: center; - margin-bottom: 20px; +.login-brand::after { + width: 360px; + height: 360px; + background: radial-gradient(circle, rgba(30, 144, 255, 0.15) 0%, transparent 70%); + bottom: -120px; + left: -120px; + animation-delay: 2s; } -.logo-section h1 { - font-size: 32px; - font-weight: bold; - color: white; - margin: 0 0 8px 0; +@keyframes brandPulse { + 0%, + 100% { + transform: scale(1); + opacity: 0.3; + } + 50% { + transform: scale(1.2); + opacity: 0.7; + } } -.subtitle { - color: #36dad6; - font-size: 13px; - margin: 0; +.brand-particle { + position: absolute; + width: 4px; + height: 4px; + background: #40e0d0; + border-radius: 50%; + opacity: 0.4; + animation: floatParticle 18s infinite; +} + +.brand-particle-1 { + left: 12%; + top: 25%; +} +.brand-particle-2 { + left: 30%; + top: 70%; + animation-delay: 2s; +} +.brand-particle-3 { + left: 52%; + top: 45%; + animation-delay: 4s; +} +.brand-particle-4 { + left: 68%; + top: 80%; + animation-delay: 1s; +} +.brand-particle-5 { + left: 82%; + top: 35%; + animation-delay: 3s; +} +.brand-particle-6 { + left: 20%; + top: 85%; + animation-delay: 5s; +} + +@keyframes floatParticle { + 0%, + 100% { + transform: translate(0, 0); + opacity: 0.4; + } + 50% { + transform: translate(25px, -60px); + opacity: 0.8; + } +} + +.brand-content { + position: relative; + z-index: 1; + text-align: center; + max-width: 420px; + display: flex; + flex-direction: column; + gap: 1.5rem; + align-items: center; } -.login-form { - background: #1e293b; - border-radius: 16px; - padding: 32px; - border: 1px solid #334155; +.brand-kicker { + text-transform: uppercase; + letter-spacing: 3px; + color: #40e0d0; + font-size: 0.9rem; + margin-bottom: 0.5rem; } -.form-header { +.brand-logo { + width: 180px; + height: auto; + margin: 0 auto 1rem; + filter: drop-shadow(0 10px 25px rgba(64, 224, 208, 0.3)); + animation: floatLogo 4s ease-in-out infinite; +} + +@keyframes floatLogo { + 0%, + 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-12px); + } +} + +.brand-text h1 { + font-size: clamp(1.8rem, 4vw, 2.3rem); + margin-bottom: 1rem; + background: linear-gradient(135deg, #ffffff, #40e0d0); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; text-align: center; - margin-bottom: 24px; -} - -.form-header h2 { - font-size: 20px; - font-weight: 600; - color: white; - margin: 0 0 8px 0; } -.form-header p { - color: #94a3b8; - font-size: 14px; +.brand-text p { + color: #b0c4de; + line-height: 1.6; margin: 0; + text-align: center; } -.form-fields { +.brand-features { + margin-top: 1rem; display: flex; flex-direction: column; - gap: 24px; + gap: 1rem; + width: 100%; } -.field-group { +.brand-feature { display: flex; - flex-direction: column; + align-items: center; + gap: 1rem; + padding: 1rem; + background: rgba(255, 255, 255, 0.03); + border-radius: 12px; + border: 1px solid rgba(64, 224, 208, 0.1); } -.field-group label { - color: #cbd5e1; - font-size: 14px; - font-weight: 500; - margin-bottom: 8px; +.brand-feature-icon { + width: 40px; + height: 40px; + display: grid; + place-items: center; + background: linear-gradient(135deg, #40e0d0, #1e90ff); + border-radius: 10px; + font-size: 1.2rem; +} + +.login-form-section { + /* background: #0f1f38; */ + display: flex; + align-items: stretch; + justify-content: center; + /* padding: 2.5rem; */ + border-radius: 24px; } -.field-group input { - padding: 12px 16px; - background: #334155; - border: 1px solid #475569; - border-radius: 8px; - color: white; - font-size: 14px; +.login-form-card { + width: 100%; + max-width: 480px; + background: rgba(15, 35, 56, 0.9); + border-radius: 18px; + border: none; + padding: 2.25rem; + box-shadow: 0 30px 60px rgba(5, 9, 20, 0.45); + height: 100%; } -.field-group input::placeholder { - color: #94a3b8; +.login-form-header h2 { + font-size: 2rem; + margin-bottom: 0.5rem; } -.field-group input:focus { - outline: none; - border-color: #36dad6; - box-shadow: 0 0 0 1px #36dad6; +.login-form-header p { + color: #b0c4de; +} + +.login-form { + margin-top: 2rem; + display: flex; + flex-direction: column; + gap: 1.25rem; } -.password-field { +.form-group label { + display: block; + margin-bottom: 0.4rem; + color: #b0c4de; + font-weight: 500; +} + +.input-wrapper { position: relative; - width: 100%; } -.password-field input { +.input-wrapper input { width: 100%; - padding-right: 48px; + padding: 1rem 1rem 1rem 3rem; + border-radius: 12px; + border: 2px solid rgba(64, 224, 208, 0.2); + background: rgba(255, 255, 255, 0.05); + color: #ffffff; + font-size: 1rem; +} + +.input-wrapper input::placeholder { + color: #6c7a8d; +} + +.input-wrapper input:focus { + outline: none; + border-color: #40e0d0; + background: rgba(255, 255, 255, 0.08); + box-shadow: 0 0 0 4px rgba(64, 224, 208, 0.12); +} + +.input-icon { + position: absolute; + left: 1rem; + top: 50%; + transform: translateY(-50%); + color: #40e0d0; } .password-toggle { position: absolute; - right: 16px; + right: 1rem; top: 50%; transform: translateY(-50%); background: none; border: none; - color: #94a3b8; + color: #b0c4de; cursor: pointer; - font-size: 16px; } .password-toggle:hover { - color: #cbd5e1; + color: #40e0d0; } .form-options { display: flex; justify-content: space-between; align-items: center; + font-size: 0.9rem; } -.checkbox-label { +.checkbox-wrapper { display: flex; align-items: center; - color: #cbd5e1; - font-size: 14px; + gap: 0.5rem; cursor: pointer; + color: #b0c4de; } -.checkbox-label input { - margin-right: 8px; - width: 16px; - height: 16px; +.checkbox-wrapper input { + width: 18px; + height: 18px; + accent-color: #40e0d0; } .forgot-link { - color: #36dad6; - font-size: 14px; + color: #40e0d0; text-decoration: none; } -.forgot-link:hover { - color: #36dad6; -} - - -.signin-btn { +.btn-signin { width: 100%; - background: #36dad6; - color: #ffffff; - font-weight: 600; - padding: 12px 16px; + padding: 1rem; + border-radius: 12px; border: none; - border-radius: 8px; - font-size: 16px; + background: linear-gradient(135deg, #40e0d0, #1e90ff); + color: #ffffff; + font-size: 1rem; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; cursor: pointer; - transition: transform 0.15s ease, filter 0.15s ease; -} - -.signin-btn:hover { - filter: brightness(0.95); - transform: translateY(-1px); -} - -.signin-btn:focus-visible { - outline: 2px solid #ffffff; - outline-offset: 2px; + transition: transform 0.2s ease, box-shadow 0.2s ease; } - -.security-notice { - margin-top: 24px; - padding: 16px; - background: #334155; - border-radius: 8px; - border: 1px solid #475569; +.btn-signin:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(64, 224, 208, 0.4); } -.security-content { +.divider { display: flex; - align-items: flex-start; + align-items: center; + margin: 2rem 0; + color: #b0c4de; + text-transform: uppercase; + font-size: 0.8rem; + letter-spacing: 1px; } -.security-icon { - font-size: 20px; - margin-right: 12px; - margin-top: 2px; +.divider::before, +.divider::after { + content: ""; + flex: 1; + height: 1px; + background: rgba(64, 224, 208, 0.2); } -.security-content h3 { - font-size: 14px; - font-weight: 500; - color: white; - margin: 0 0 4px 0; +.divider span { + padding: 0 1rem; } -.security-content p { - font-size: 12px; - color: #94a3b8; - margin: 0; - line-height: 1.4; +.social-login { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; + margin-bottom: 1.5rem; } - -.login-right { - width: 70%; - position: relative; - overflow: hidden; +.social-login.single { + grid-template-columns: 1fr; + justify-items: center; } -.login-right img { +.social-btn { + padding: 0.9rem; + border-radius: 12px; + border: 2px solid rgba(64, 224, 208, 0.2); + background: rgba(255, 255, 255, 0.04); + color: #ffffff; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + cursor: pointer; + transition: transform 0.2s ease, border-color 0.2s ease; width: 100%; - height: 100%; - object-fit: cover; + max-width: 280px; } -.image-overlay { - position: absolute; - inset: 0; - background: linear-gradient(to right, rgba(15, 23, 42, 0.8), rgba(15, 23, 42, 0.4), transparent); +.social-btn:hover { + transform: translateY(-2px); + border-color: #40e0d0; } +.social-icon { + width: 32px; + height: 32px; + border-radius: 10px; + background: rgba(64, 224, 208, 0.15); + display: grid; + place-items: center; + font-weight: 600; + letter-spacing: 0.5px; +} -.signup-redirect { +.signup-text { text-align: center; - margin-top: 20px; - color: #94a3b8; - font-size: 14px; + color: #b0c4de; + font-size: 0.95rem; } -.signup-link { +.signup-text button { background: none; border: none; - color: #36dad6; + color: #40e0d0; font-weight: 600; cursor: pointer; - text-decoration: underline; - font-size: 14px; - font-family: inherit; } -.signup-link:hover { - color: #f5f6f6; +.signup-text button:hover { + color: #1e90ff; +} + +.security-info { + display: flex; + gap: 1rem; + align-items: center; + padding: 1rem; + border-radius: 12px; + border: 1px solid rgba(64, 224, 208, 0.2); + background: rgba(64, 224, 208, 0.05); + margin-top: 1.5rem; +} + +.security-icon-box { + width: 44px; + height: 44px; + border-radius: 12px; + background: linear-gradient(135deg, #40e0d0, #1e90ff); + display: flex; + align-items: center; + justify-content: center; +} + +.security-info-text h4 { + margin: 0; + font-size: 0.95rem; +} + +.security-info-text p { + margin: 0; + color: #b0c4de; + font-size: 0.85rem; +} + +@media (max-width: 1024px) { + .login-main { + grid-template-columns: 1fr; + } + + .login-brand { + min-height: 60vh; + } + + .brand-features { + display: none; + } +} + +@media (max-width: 640px) { + .auth-header { + padding: 1rem 1.5rem; + } + + .auth-nav { + gap: 1rem; + } + + .login-brand { + padding: 2.5rem 1.5rem; + } + + .login-form-section { + padding: 2.5rem 1.5rem; + } + + .social-login { + grid-template-columns: 1fr; + } } diff --git a/frontend/src/pages/Auth/LoginPage.js b/frontend/src/pages/Auth/LoginPage.js index a9cee6a4..7f0d1230 100644 --- a/frontend/src/pages/Auth/LoginPage.js +++ b/frontend/src/pages/Auth/LoginPage.js @@ -1,112 +1,22 @@ -import React, { useState } from 'react'; -import './LoginPage.css'; - -export default function LoginPage({ onLogin, onSignUpClick }) { - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [showPassword, setShowPassword] = useState(false); - const [rememberMe, setRememberMe] = useState(false); - - const handleSubmit = () => { - onLogin(); - }; - +import React from "react"; +import "./LoginPage.css"; +import "../Landing/LandingPage.css"; +import LoginHeader from "./components/LoginHeader"; +import BrandPanel from "./components/BrandPanel"; +import SignInPanel from "./components/SignInPanel"; +import LandingFooter from "../Landing/components/LandingFooter"; + +const LoginPage = ({ onLogin, onSignUpClick }) => { return ( -
- - -
- Login bg -
-
- -
-
-
- AutoAudit Logo -

AutoAudit

-

Microsoft 365 Compliance Platform

-
- -
-
-

Sign In

-

Access your compliance dashboard and security insights

-
- -
-
- - setEmail(e.target.value)} - placeholder="your.email@company.com" - /> -
- -
- -
- setPassword(e.target.value)} - placeholder="Enter your password" - /> - -
-
- -
- - Forgot password? -
- - - -
- Don't have an account? - -
-
- -
-
- 🛡️ -
-

Secure Enterprise Access

-

Your connection is encrypted and monitored for compliance with enterprise security standards

-
-
-
-
-
-
+
+ +
+ + +
+
); -} \ No newline at end of file +}; + +export default LoginPage; diff --git a/frontend/src/pages/Auth/components/BrandPanel.js b/frontend/src/pages/Auth/components/BrandPanel.js new file mode 100644 index 00000000..6ebcfc34 --- /dev/null +++ b/frontend/src/pages/Auth/components/BrandPanel.js @@ -0,0 +1,43 @@ +import React from "react"; + +const brandFeatures = [ + { icon: "🔒", text: "Enterprise-grade security & encryption" }, + { icon: "⚡", text: "Real-time compliance monitoring" }, + { icon: "📊", text: "Actionable reporting & insights" }, +]; + +const BrandPanel = () => { + return ( +
+ {Array.from({ length: 6 }).map((_, index) => ( + + ))} + +
+ AutoAudit + +
+

Access security insights anywhere

+

+ Connect to your Microsoft 365 compliance dashboard, monitor security posture, and act on + real-time recommendations. +

+
+ +
+ {brandFeatures.map((feature) => ( +
+ {feature.icon} + {feature.text} +
+ ))} +
+
+
+ ); +}; + +export default BrandPanel; diff --git a/frontend/src/pages/Auth/components/LoginHeader.js b/frontend/src/pages/Auth/components/LoginHeader.js new file mode 100644 index 00000000..c6b8a4a2 --- /dev/null +++ b/frontend/src/pages/Auth/components/LoginHeader.js @@ -0,0 +1,29 @@ +import React from "react"; + +const navLinks = [ + { label: "Home", href: "/" }, + { label: "Features", href: "/#features" }, + { label: "Benefits", href: "/#benefits" }, + { label: "About", href: "/about" }, + { label: "Contact", href: "/contact" }, +]; + +const LoginHeader = () => { + return ( +
+ + AutoAudit + + + +
+ ); +}; + +export default LoginHeader; diff --git a/frontend/src/pages/Auth/components/SignInPanel.js b/frontend/src/pages/Auth/components/SignInPanel.js new file mode 100644 index 00000000..3b9f8dc4 --- /dev/null +++ b/frontend/src/pages/Auth/components/SignInPanel.js @@ -0,0 +1,136 @@ +import React, { useState } from "react"; +import { + ArrowRight, + Eye, + EyeOff, + Lock, + Mail, + ShieldCheck, +} from "lucide-react"; + +const SignInPanel = ({ onLogin, onSignUpClick }) => { + const [formData, setFormData] = useState({ + email: "", + password: "", + remember: false, + }); + const [showPassword, setShowPassword] = useState(false); + + const handleChange = (event) => { + const { name, value, type, checked } = event.target; + setFormData((prev) => ({ + ...prev, + [name]: type === "checkbox" ? checked : value, + })); + }; + + const handleSubmit = (event) => { + event.preventDefault(); + if (onLogin) { + onLogin(formData); + } + }; + + return ( +
+
+
+

Welcome Back

+

Sign in to access your compliance dashboard and security reports.

+
+ +
+
+ +
+ + +
+
+ +
+ +
+ + + +
+
+ +
+ + + Forgot password? + +
+ + +
+ +
+ or continue with +
+ +
+ +
+ +

+ Don't have an account?{" "} + +

+ +
+
+ +
+
+

Secure Enterprise Access

+

Your connection is encrypted and monitored for compliance.

+
+
+
+
+ ); +}; + +export default SignInPanel; From 9fc77e9e7814257f51c99bea606cd55dace463cf Mon Sep 17 00:00:00 2001 From: Romil Bijarnia Date: Thu, 4 Dec 2025 18:55:58 +0530 Subject: [PATCH 006/111] evidence scanner push, multiple document types are supported for uploading evidences. --- backend-api/Dockerfile | 21 +- backend-api/app/api/v1/evidence.py | 68 ++++ backend-api/app/api/v1/router.py | 3 +- backend-api/app/main.py | 11 + backend-api/pyproject.toml | 9 + docker-compose.yml | 5 +- frontend/package-lock.json | 22 ++ frontend/src/App.js | 6 +- frontend/src/components/Sidebar.js | 13 +- frontend/src/pages/Evidence.css | 171 ++++++++- frontend/src/pages/Evidence.js | 349 +++++++++++++----- frontend/src/utils/api.js | 38 ++ security/Dockerfile | 28 ++ security/backend/reportgenerator.py | 2 +- security/backend/scanner.py | 7 +- security/frontend/ui.py | 283 ++++++++++---- security/reports/report_service.py | 58 ++- security/requirements.txt | 3 +- security/strategies/__init__.py | 30 +- security/strategies/application_control.py | 61 ++- .../strategies/configure_macro_settings.py | 26 +- security/strategies/custom_benchmarks.py | 204 ++++++++++ .../strategies/multi-factor_authentication.py | 22 +- tmp_test_pkg/pkg/__init__.py | 1 + 24 files changed, 1212 insertions(+), 229 deletions(-) create mode 100644 backend-api/app/api/v1/evidence.py create mode 100644 frontend/src/utils/api.js create mode 100644 security/Dockerfile create mode 100644 security/strategies/custom_benchmarks.py create mode 100644 tmp_test_pkg/pkg/__init__.py diff --git a/backend-api/Dockerfile b/backend-api/Dockerfile index b2eb1d8d..4fd512c9 100644 --- a/backend-api/Dockerfile +++ b/backend-api/Dockerfile @@ -3,25 +3,34 @@ FROM python:3.11-slim WORKDIR /app -# Install system dependencies +# Install system dependencies (incl. Tesseract for evidence OCR) RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ + tesseract-ocr \ + libtesseract-dev \ && rm -rf /var/lib/apt/lists/* # Install uv RUN pip install uv # Copy dependency files -COPY pyproject.toml uv.lock ./ +COPY backend-api/pyproject.toml backend-api/uv.lock ./ # Install dependencies RUN uv sync --frozen --no-dev +# Security (evidence) dependencies into the uv venv +COPY security/requirements.txt ./security/requirements.txt +RUN uv pip install --python .venv/bin/python -r security/requirements.txt + # Copy application code -COPY app ./app -COPY alembic ./alembic -COPY alembic.ini ./ -COPY entrypoint.sh ./ +COPY backend-api/app ./app +COPY backend-api/alembic ./alembic +COPY backend-api/alembic.ini ./ +COPY backend-api/entrypoint.sh ./ + +# Copy evidence scanner assets/logic +COPY security ./security # Make entrypoint executable RUN chmod +x entrypoint.sh diff --git a/backend-api/app/api/v1/evidence.py b/backend-api/app/api/v1/evidence.py new file mode 100644 index 00000000..d2f2b2f0 --- /dev/null +++ b/backend-api/app/api/v1/evidence.py @@ -0,0 +1,68 @@ +from fastapi import APIRouter, UploadFile, File, Form +from fastapi.responses import JSONResponse, RedirectResponse, FileResponse + +# Ensure the monorepo /security package is importable both locally and inside Docker +import sys +from pathlib import Path + + +def _find_security_dir() -> Path | None: + here = Path(__file__).resolve() + for ancestor in here.parents: + candidate = ancestor / "security" + if candidate.exists(): + return candidate + return None + + +SECURITY_DIR = _find_security_dir() +if SECURITY_DIR and str(SECURITY_DIR) not in sys.path: + sys.path.append(str(SECURITY_DIR)) + +# Reuse existing evidence logic from security package +from security.frontend import ui as evidence_ui + +router = APIRouter(prefix="/evidence", tags=["evidence"]) + + +@router.get("/strategies") +async def strategies(): + # delegate to evidence module + return evidence_ui.api_strategies() + + +@router.get("/health") +async def health(): + return evidence_ui.health() + + +@router.get("/scan-mem") +async def scan_mem(): + """Serve the human-friendly recent scans page (HTML).""" + return evidence_ui.scan_mem_page() + + +@router.get("/recent-scans", include_in_schema=False) +async def recent_scans_redirect(): + return RedirectResponse(url="/v1/evidence/scan-mem") + + +@router.get("/scan-log", include_in_schema=False) +async def scan_log_redirect(): + return RedirectResponse(url="/v1/evidence/scan-mem") + + +@router.post("/scan") +async def scan( + evidence: UploadFile = File(...), + strategy_name: str = Form(...), + user_id: str = Form("user"), +): + # delegate to existing implementation + return await evidence_ui.scan(evidence=evidence, strategy_name=strategy_name, user_id=user_id) + + +@router.get("/reports/{filename}") +async def download_report(filename: str): + # reuse existing download handler + return evidence_ui.download_report(filename) diff --git a/backend-api/app/api/v1/router.py b/backend-api/app/api/v1/router.py index 611e079d..63402d2b 100644 --- a/backend-api/app/api/v1/router.py +++ b/backend-api/app/api/v1/router.py @@ -1,5 +1,5 @@ from fastapi import APIRouter -from app.api.v1 import exports, audit, auth, test +from app.api.v1 import exports, audit, auth, test, evidence api_router = APIRouter() @@ -12,3 +12,4 @@ # Existing routes api_router.include_router(exports.router) api_router.include_router(audit.router) +api_router.include_router(evidence.router) diff --git a/backend-api/app/main.py b/backend-api/app/main.py index 94d70014..c12ff353 100644 --- a/backend-api/app/main.py +++ b/backend-api/app/main.py @@ -1,4 +1,5 @@ from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware from app.core.logging import setup_logging from app.api.v1.router import api_router from app.core.config import get_settings @@ -12,6 +13,16 @@ def create_app() -> FastAPI: setup_logging() app = FastAPI(title="AutoAudit API", version="0.1.0") + # Allow frontend (localhost:3000 and others) to call the API during development. + # Keep permissive for now; tighten via reverse proxy in production if needed. + app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # permissive for dev; adjust in prod + allow_credentials=False, # must be False when using wildcard origins + allow_methods=["*"], + allow_headers=["*"], + ) + app.add_middleware(RequestLoggingMiddleware) app.include_router(api_router, prefix=settings.API_PREFIX) diff --git a/backend-api/pyproject.toml b/backend-api/pyproject.toml index 4519e319..31c0c835 100644 --- a/backend-api/pyproject.toml +++ b/backend-api/pyproject.toml @@ -14,4 +14,13 @@ dependencies = [ "sqlalchemy>=2.0.0", "asyncpg>=0.29.0", "alembic>=1.13.0", + # Evidence scanner dependencies (shared with security module) + "pytesseract", + "pillow", + "opencv-python", + "python-docx", + "docx2pdf", + "pymupdf", + "python-multipart", + "fpdf2", ] diff --git a/docker-compose.yml b/docker-compose.yml index 43d1ad3d..795f9c68 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,8 +53,8 @@ services: backend-api: profiles: ["frontend-dev", "all"] build: - context: ./backend-api - dockerfile: Dockerfile + context: . + dockerfile: backend-api/Dockerfile container_name: autoaudit-backend-api environment: - APP_ENV=dev @@ -78,6 +78,7 @@ services: container_name: autoaudit-frontend environment: - REACT_APP_API_URL=http://localhost:8000 + - REACT_APP_EVIDENCE_API_BASE=http://localhost:8000 ports: - "3000:3000" restart: unless-stopped diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 411ec4c1..666a7b0d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -86,6 +86,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -735,6 +736,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.27.1.tgz", "integrity": "sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1618,6 +1620,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", @@ -3452,6 +3455,7 @@ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -3929,6 +3933,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", "@typescript-eslint/scope-manager": "5.62.0", @@ -3982,6 +3987,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -4351,6 +4357,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4449,6 +4456,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5350,6 +5358,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001737", "electron-to-chromium": "^1.5.211", @@ -7200,6 +7209,7 @@ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -9966,6 +9976,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^27.5.1", "import-local": "^3.0.2", @@ -12251,6 +12262,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -13438,6 +13450,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -13797,6 +13810,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -13928,6 +13942,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -13952,6 +13967,7 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -14457,6 +14473,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "license": "MIT", + "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -14699,6 +14716,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -16336,6 +16354,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "license": "(MIT OR CC0-1.0)", + "peer": true, "engines": { "node": ">=10" }, @@ -16765,6 +16784,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -16836,6 +16856,7 @@ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "license": "MIT", + "peer": true, "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -17248,6 +17269,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", diff --git a/frontend/src/App.js b/frontend/src/App.js index aba1b3b0..186052c4 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -30,10 +30,10 @@ const ProtectedRoute = ({ children, isAuthenticated }) => { }; // Dashboard Layout Component (with sidebar) -const DashboardLayout = ({ children, sidebarWidth, isDarkMode, onThemeToggle }) => { +const DashboardLayout = ({ children, sidebarWidth, isDarkMode, onThemeToggle, onSidebarWidthChange }) => { return ( <> - {}} isDarkMode={isDarkMode} /> + {React.cloneElement(children, { sidebarWidth, isDarkMode, onThemeToggle })} ); @@ -138,6 +138,7 @@ function App() { sidebarWidth={sidebarWidth} isDarkMode={isDarkMode} onThemeToggle={handleThemeToggle} + onSidebarWidthChange={handleSidebarWidthChange} > diff --git a/frontend/src/components/Sidebar.js b/frontend/src/components/Sidebar.js index 881f2f16..299bde48 100644 --- a/frontend/src/components/Sidebar.js +++ b/frontend/src/components/Sidebar.js @@ -35,13 +35,11 @@ const NavButton = ({ href, name, icon, isExpanded, isActive = false, onClick }) }; // Main sidebar component -const Sidebar = ({ onWidthChange, isDarkMode = true }) => { +const Sidebar = ({ onWidthChange = () => {}, isDarkMode = true }) => { const [isExpanded, setIsExpanded] = useState(true); //Track whether sidebar is expanded const [activeItem, setActiveItem] = useState('home'); // Track active navigation item const [searchValue, setSearchValue] = useState(''); // Track search input value - // Add this line right after the component declaration for debugging -console.log('Sidebar isDarkMode:', isDarkMode); //Event to toggle collapsed state and notify parents that the width has changed const toggleSidebar = () => { const newExpanded = !isExpanded; @@ -66,9 +64,8 @@ console.log('Sidebar isDarkMode:', isDarkMode); {/* only display when the navbar is expanded! */} {isExpanded ? (
- 🔍
) : ( - )} @@ -146,4 +143,4 @@ console.log('Sidebar isDarkMode:', isDarkMode); ); }; -export default Sidebar; \ No newline at end of file +export default Sidebar; diff --git a/frontend/src/pages/Evidence.css b/frontend/src/pages/Evidence.css index a0fc0c8a..818df3b5 100644 --- a/frontend/src/pages/Evidence.css +++ b/frontend/src/pages/Evidence.css @@ -17,7 +17,7 @@ background: var(--bg-primary, #0f172a); padding: 24px; color: var(--text-primary, #ffffff); - transition: all 0.3s ease; + transition: color 0.2s ease, background 0.2s ease; } .evidence-scanner.light { @@ -45,7 +45,8 @@ } .evidence-container { - max-width: 1060px; + width: 100%; + max-width: 100%; margin: 0 auto; } @@ -89,6 +90,47 @@ text-align: left; } +.pill-row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 10px; + margin: 0 0 16px 24px; +} + +.pill { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + border: 1px solid var(--border-color, #334155); + border-radius: 999px; + background: var(--bg-secondary, #1e293b); + color: var(--text-primary); + font-size: 12px; +} + +.pill-label { + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--text-secondary); +} + +.pill-value { + font-weight: 600; +} + +.pill-ok { + border-color: var(--success); + color: var(--success); +} + +.pill-warn { + border-color: var(--warning); + color: var(--warning); +} + /* Navigation Back Button */ .nav-breadcrumb { display: flex; @@ -162,6 +204,20 @@ background: var(--bg-primary, #0f172a); color: var(--text-primary); transition: all 0.3s ease; + border-radius: 10px; +} + +.form-select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%2394a3b8' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 12px center; + background-size: 12px 8px; + padding-right: 40px; + min-height: 46px; + line-height: 1.4; } .form-select:focus, @@ -271,6 +327,17 @@ font-size: 14px; } +.note-banner { + border: 1px solid var(--border-color, #334155); + border-left: 4px solid var(--teal); + background: var(--bg-secondary, #1e293b); + color: var(--text-primary); + padding: 12px 14px; + border-radius: 10px; + margin: 0 0 20px 0; + line-height: 1.4; +} + .spinner { width: 16px; height: 16px; @@ -293,6 +360,8 @@ background: var(--bg-secondary, #1e293b); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; + width: 100%; + overflow-x: auto; } .results-header { @@ -334,6 +403,7 @@ border-collapse: collapse; margin-top: 16px; font-size: 14px; + min-width: 1280px; } .results-table th, @@ -344,6 +414,80 @@ vertical-align: top; } +.results-table th:nth-child(6), +.results-table td:nth-child(6) { + min-width: 320px; + max-width: 520px; + white-space: normal; + word-break: break-word; +} + +.results-table th:nth-child(7), +.results-table td:nth-child(7) { + min-width: 520px; + white-space: normal; +} + +.evidence-cell { + display: flex; + flex-direction: column; + gap: 6px; +} + +.evidence-block { + border: none; + padding: 0; + background: transparent; +} + +.evidence-block__label { + display: none; +} + +.evidence-block__body { + margin: 0; + white-space: pre-wrap; + font-family: "SFMono-Regular", "Menlo", "Consolas", monospace; + font-size: 13px; + line-height: 1.45; + word-break: break-word; + padding: 10px 12px; + border: 1px solid var(--border-color, #334155); + border-radius: 12px; + background: var(--bg-secondary, #1e293b); +} + +.evidence-extra { + margin-top: 6px; + padding: 10px 12px; + border: 1px dashed var(--border-color, #334155); + border-radius: 10px; + background: var(--bg-primary, #0f172a); +} + +.link-button { + display: none; +} + +.link-button:hover { + text-decoration: underline; +} + +.evidence-summary { + line-height: 1.4; +} + +.evidence-full summary { + cursor: pointer; + color: var(--teal); + font-weight: 600; +} + +.evidence-full div { + margin-top: 4px; + line-height: 1.4; +} + .results-table th { background: var(--bg-tertiary, #334155); color: var(--text-primary); @@ -383,6 +527,23 @@ text-decoration: underline; } +.evidence-list { + margin: 0; + padding-left: 18px; + color: var(--text-primary); +} + +.evidence-list li { + line-height: 1.4; +} + +.error-list { + margin: 4px 0 0; + padding-left: 18px; + color: var(--error); + font-size: 13px; +} + /* Responsive Design */ @media (max-width: 768px) { .form-row { @@ -411,6 +572,10 @@ .results-table td { padding: 8px; } + + .evidence-pair { + grid-template-columns: 1fr; + } } @media (max-width: 480px) { @@ -431,4 +596,4 @@ width: 40px; height: 40px; } -} \ No newline at end of file +} diff --git a/frontend/src/pages/Evidence.js b/frontend/src/pages/Evidence.js index 4d5eb2be..2b766870 100644 --- a/frontend/src/pages/Evidence.js +++ b/frontend/src/pages/Evidence.js @@ -1,110 +1,238 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import './Evidence.css'; import { useNavigate } from 'react-router-dom'; +import { parseApiError, formatEvidenceList } from '../utils/api'; + +const EvidenceDetails = ({ details, evidence }) => { + const toText = (value) => { + if (!value) return ''; + if (Array.isArray(value)) { + return value + .filter(Boolean) + .map((item) => String(item).trim()) + .join('\n'); + } + if (typeof value === 'object') { + const stringifiable = value.observed || value.expected || value.recommendation; + if (!stringifiable) return ''; + return String(stringifiable).trim(); + } + return String(value).trim(); + }; + + const observed = toText( + details?.observed_full ?? + details?.observed ?? + (Array.isArray(details?.evidence) ? details.evidence : details?.evidence) + ); + + const evidenceList = formatEvidenceList(evidence); + const merged = [observed, evidenceList.join('\n')].filter(Boolean).join('\n').trim(); + const hasContent = merged.length > 0; + + if (!hasContent) return ; + + return ( +
+
+
+          {merged}
+        
+
+
+ ); +}; const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => { + const [observedSidebarWidth, setObservedSidebarWidth] = useState(sidebarWidth); + + useEffect(() => { + if (typeof window === 'undefined' || typeof ResizeObserver === 'undefined') { + setObservedSidebarWidth(sidebarWidth); + return; + } + const sidebarEl = document.querySelector('.sidebar'); + if (!sidebarEl) { + setObservedSidebarWidth(sidebarWidth); + return; + } + const observer = new ResizeObserver((entries) => { + const width = entries?.[0]?.contentRect?.width; + if (width && Math.abs(width - observedSidebarWidth) > 1) { + setObservedSidebarWidth(width); + } + }); + observer.observe(sidebarEl); + return () => observer.disconnect(); + }, [sidebarWidth, observedSidebarWidth]); + + const apiCandidates = useMemo(() => { + const roots = [ + process.env.REACT_APP_EVIDENCE_API_BASE, + process.env.REACT_APP_EVIDENCE_API, + process.env.REACT_APP_API_URL, + typeof window !== 'undefined' ? window.location.origin : null, + 'http://localhost:8000', + ] + .filter(Boolean) + .map((root) => root.replace(/\/+$/, '')); + + const urls = roots.map((root) => `${root}/v1/evidence`); + + return urls.filter((url, idx) => urls.indexOf(url) === idx); + }, []); + + const [apiBase, setApiBase] = useState(() => apiCandidates[0] || ''); + const [strategies, setStrategies] = useState([]); + const [strategiesLoading, setStrategiesLoading] = useState(false); + const [strategiesError, setStrategiesError] = useState(''); + const [health, setHealth] = useState(null); + const [selectedStrategy, setSelectedStrategy] = useState(''); const [userId, setUserId] = useState(''); const [selectedFile, setSelectedFile] = useState(null); const [isScanning, setIsScanning] = useState(false); const [error, setError] = useState(''); + const [errorDetails, setErrorDetails] = useState([]); + const [note, setNote] = useState(''); const [results, setResults] = useState(null); const navigate = useNavigate(); - // Mock strategies data - const strategies = [ - { name: 'CIS Microsoft 365 Audit', description: 'Comprehensive Microsoft 365 security assessment based on CIS benchmarks' }, - { name: 'NIST Compliance Check', description: 'NIST Cybersecurity Framework compliance verification' }, - { name: 'ISO 27001 Assessment', description: 'ISO 27001 information security management system audit' }, - { name: 'SOC 2 Readiness', description: 'SOC 2 Type II compliance preparation and gap analysis' }, - { name: 'GDPR Compliance Scan', description: 'General Data Protection Regulation compliance assessment' } - ]; - - // Mock results data - const mockResults = { - findings: [ - { - test_id: 'CIS-001', - sub_strategy: 'Identity & Access', - detected_level: 'High', - pass_fail: 'FAIL', - priority: 'Critical', - recommendation: 'Enable multi-factor authentication for all admin accounts', - evidence: ['Admin account "admin@company.com" does not have MFA enabled', 'Found 3 admin accounts without MFA'] - }, - { - test_id: 'CIS-002', - sub_strategy: 'Data Protection', - detected_level: 'Medium', - pass_fail: 'FAIL', - priority: 'High', - recommendation: 'Implement data loss prevention policies', - evidence: ['No DLP policies found', 'Sensitive data sharing detected'] - }, - { - test_id: 'CIS-003', - sub_strategy: 'Access Management', - detected_level: 'Low', - pass_fail: 'PASS', - priority: 'Medium', - recommendation: 'Continue monitoring access patterns', - evidence: ['Access controls properly configured', 'Regular access reviews in place'] - }, - { - test_id: 'CIS-004', - sub_strategy: 'Audit Logging', - detected_level: 'Medium', - pass_fail: 'WARNING', - priority: 'Medium', - recommendation: 'Extend audit log retention period', - evidence: ['Audit logs retained for 90 days', 'Recommended retention: 1 year'] - }, - { - test_id: 'CIS-005', - sub_strategy: 'Email Security', - detected_level: 'High', - pass_fail: 'FAIL', - priority: 'Critical', - recommendation: 'Configure advanced threat protection', - evidence: ['ATP not enabled', 'Phishing protection insufficient'] + useEffect(() => { + let isActive = true; + + const fetchStrategies = async () => { + setStrategiesLoading(true); + setStrategiesError(''); + + for (const base of apiCandidates) { + try { + const res = await fetch(`${base}/strategies`); + if (!res.ok) { + const parsed = await parseApiError(res, `Failed to load strategies (${res.status})`); + throw new Error(parsed.message); + } + const data = await res.json(); + if (!isActive) { + return; + } + + setStrategies(Array.isArray(data) ? data : []); + setApiBase(base); + setStrategiesLoading(false); + return; + } catch (err) { + // try the next candidate + } } - ], - reports: ['report-001.pdf', 'report-002.pdf', 'report-003.pdf', 'report-004.pdf', 'report-005.pdf'] - }; + + if (isActive) { + setStrategiesError('Unable to load strategies from the Evidence API.'); + setStrategies([]); + setApiBase(apiCandidates[0] || ''); + setStrategiesLoading(false); + } + }; + + fetchStrategies(); + return () => { isActive = false; }; + }, [apiCandidates]); + + useEffect(() => { + if (!apiBase) { + return; + } + let isActive = true; + + const fetchHealth = async () => { + try { + const res = await fetch(`${apiBase}/health`); + if (!res.ok) return; + const data = await res.json(); + if (isActive) setHealth(data); + } catch (err) { + // health is best-effort + } + }; + + fetchHealth(); + return () => { isActive = false; }; + }, [apiBase]); const handleStrategyChange = (e) => { setSelectedStrategy(e.target.value); setError(''); + setErrorDetails([]); + setResults(null); + setNote(''); }; const handleFileChange = (e) => { - const file = e.target.files[0]; - setSelectedFile(file); + const file = e.target.files?.[0]; + setSelectedFile(file || null); setError(''); + setErrorDetails([]); }; const handleScan = async () => { setError(''); - + setErrorDetails([]); + setNote(''); + const base = apiBase || apiCandidates[0]; + + if (!base) { + setError('Evidence API is not configured.'); + return; + } + if (!selectedStrategy) { setError('Please select a strategy.'); return; } - + if (!selectedFile) { setError('Please choose an evidence file.'); return; } setIsScanning(true); - - // Simulate API call + + const formData = new FormData(); + formData.append('strategy_name', selectedStrategy); + formData.append('user_id', userId || 'user'); + formData.append('evidence', selectedFile); + try { - await new Promise(resolve => setTimeout(resolve, 3000)); // 3 second delay - setResults(mockResults); + const res = await fetch(`${base}/scan`, { + method: 'POST', + body: formData + }); + + const raw = await res.text(); + let data = {}; + try { + data = JSON.parse(raw); + } catch (err) { + throw new Error(raw?.slice(0, 160) || 'Scan failed.'); + } + + if (!res.ok || data.ok === false) { + const errs = Array.isArray(data?.errors) ? data.errors : []; + const errMsg = + errs[0]?.message || + data?.detail || + `Scan failed (${res.status})`; + setErrorDetails(errs); + throw new Error(errMsg); + } + + setResults(data); + setNote(data?.note || ''); + setErrorDetails(Array.isArray(data?.errors) ? data.errors : []); } catch (err) { - setError('Scan failed. Please try again.'); + setError(err?.message || 'Scan failed. Please try again.'); + setResults(null); } finally { setIsScanning(false); } @@ -119,15 +247,30 @@ const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => { } }; - const selectedStrategyData = strategies.find(s => s.name === selectedStrategy); + const selectedStrategyData = useMemo( + () => strategies.find((s) => s.name === selectedStrategy), + [strategies, selectedStrategy] + ); + + const openRecentScans = () => { + const base = apiBase || apiCandidates[0]; + if (base) { + window.open(`${base}/scan-mem`, '_blank', 'noopener'); + } + }; + + const buildReportLink = (filename) => { + const base = apiBase || apiCandidates[0] || ''; + return base ? `${base}/reports/${encodeURIComponent(filename)}` : '#'; + }; return (
@@ -149,11 +292,16 @@ const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => {

Evidence Scanner

- +

Your Evidence Assistant: Pick a strategy and upload your file. Images, PDF, DOCX, TXT, logs, registry exports are supported.

+
+ {strategiesLoading && Loading strategies…} + {strategiesError && {strategiesError}} +
+ {/* Main Form Card */}
@@ -164,10 +312,11 @@ const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => { className="form-select" value={selectedStrategy} onChange={handleStrategyChange} + disabled={strategiesLoading || !strategies.length} > - - {strategies.map((strategy, index) => ( - + {strategies.map((strategy) => ( + ))} @@ -175,6 +324,9 @@ const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => { {selectedStrategyData && (
{selectedStrategyData.description}
)} + {!strategiesLoading && !strategies.length && !strategiesError && ( +
No strategies returned by the API.
+ )}
@@ -196,7 +348,7 @@ const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => { id="file" type="file" className="form-file" - disabled={!selectedStrategy} + disabled={!selectedStrategy || strategiesLoading} accept=".pdf,.png,.jpg,.jpeg,.tif,.tiff,.bmp,.webp,.txt,.docx,.csv,.log,.reg,.ini,.json,.xml,.htm,.html" onChange={handleFileChange} /> @@ -223,11 +375,32 @@ const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => { 'Scan Evidence' )} - + + + {error && {error}} + {errorDetails.length > 0 && ( +
    + {errorDetails.map((err, idx) => ( +
  • + {err.code ? `[${err.code}] ` : ''}{err.message || JSON.stringify(err)} +
  • + ))} +
+ )}
+ {note && ( +
{note}
+ )} + {/* Results Section */} {results && (
@@ -254,7 +427,7 @@ const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => { Status Priority Recommendation - Evidence Extract + Evidence / Evidence Extract Report @@ -270,17 +443,15 @@ const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => { {finding.priority} {finding.recommendation} - {Array.isArray(finding.evidence) - ? finding.evidence.map((item, i) => ( -
{item}
- )) - : finding.evidence - } + - {results.reports[index] && ( + {results.reports?.[index] && ( { ); }; -export default Evidence; \ No newline at end of file +export default Evidence; diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js new file mode 100644 index 00000000..482a0fd2 --- /dev/null +++ b/frontend/src/utils/api.js @@ -0,0 +1,38 @@ +export const normalizeEvidenceItems = (evidence) => { + if (!evidence) return []; + + const parts = Array.isArray(evidence) + ? evidence + : String(evidence) + .split(/[\n;]+/) + .map((item) => item.trim()); + + return parts + .map((item) => item.replace(/^[\s#*+\-•]+/, '').trim()) + .filter(Boolean); +}; + +export const formatEvidenceList = (evidence) => normalizeEvidenceItems(evidence); + +export const parseApiError = async (res, fallback = 'Request failed') => { + try { + const clone = res.clone(); + const data = await clone.json(); + const errors = Array.isArray(data?.errors) ? data.errors : []; + const hasErrors = data?.has_errors ?? errors.length > 0; + const message = + errors[0]?.message || + data?.detail || + data?.message || + fallback; + const code = data?.code || errors[0]?.code; + return { message: message || fallback, code, errors, hasErrors }; + } catch (err) { + try { + const text = await res.text(); + return { message: text || fallback, code: null, errors: [], hasErrors: false }; + } catch (_) { + return { message: fallback, code: null, errors: [], hasErrors: false }; + } + } +}; diff --git a/security/Dockerfile b/security/Dockerfile new file mode 100644 index 00000000..c75937ed --- /dev/null +++ b/security/Dockerfile @@ -0,0 +1,28 @@ +# Evidence API Dockerfile +# Builds a lightweight FastAPI service that powers the Evidence Scanner UI. + +FROM python:3.11-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +WORKDIR /app + +# System packages needed for OCR (Tesseract). Keep the image small by avoiding +# optional PDF converters (LibreOffice) — the app falls back gracefully. +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + tesseract-ocr \ + libtesseract-dev && \ + rm -rf /var/lib/apt/lists/* + +# Python dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Application code +COPY . . + +EXPOSE 8000 + +CMD ["uvicorn", "frontend.ui:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/security/backend/reportgenerator.py b/security/backend/reportgenerator.py index a24da5be..da296ebd 100644 --- a/security/backend/reportgenerator.py +++ b/security/backend/reportgenerator.py @@ -5,7 +5,7 @@ from pathlib import Path sys.path.append(str(Path(__file__).resolve().parents[1])) from reports.report_service import generate_pdf -from strategies import load_strategies +from security.strategies import load_strategies #------------------------ Import core_ocr.py---------------- from backend.core_ocr import ( diff --git a/security/backend/scanner.py b/security/backend/scanner.py index 7c3ec7f9..b76f4b07 100644 --- a/security/backend/scanner.py +++ b/security/backend/scanner.py @@ -5,7 +5,7 @@ import sys from pathlib import Path sys.path.append(str(Path(__file__).resolve().parents[1])) -from strategies import load_strategies +from security.strategies import load_strategies #------------------------ Import core_ocr.py---------------- from backend.core_ocr import extract_text_and_preview, SUPPORTED_ALL_EXTS @@ -167,7 +167,10 @@ def main(): rows_added = 0 # track whether any hits were written if hasattr(strat, "emit_hits"): - rows = strat.emit_hits(raw_text, source_file=fpath.name) + try: + rows = strat.emit_hits(raw_text, source_file=fpath.name, user_id=user_id) + except TypeError: + rows = strat.emit_hits(raw_text) for r in rows: report_rows.append(( user_id, diff --git a/security/frontend/ui.py b/security/frontend/ui.py index 472cbe2b..a88b17e0 100644 --- a/security/frontend/ui.py +++ b/security/frontend/ui.py @@ -10,7 +10,7 @@ from fastapi import FastAPI, File, Form, HTTPException, UploadFile from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, RedirectResponse # ✨ added RedirectResponse -from PIL import Image, UnidentifiedImageError +from PIL import Image, UnidentifiedImageError, ImageOps, ImageFilter, ImageStat from pytesseract import TesseractNotFoundError from fastapi.staticfiles import StaticFiles @@ -25,9 +25,9 @@ DocxDocument = None # -------------------- Import modules -------------------- - -from reports.report_service import generate_pdf -from strategies import load_strategies +# Ensure package imports resolve when embedded under backend-api +from security.reports.report_service import generate_pdf +from security.strategies import load_strategies, ALLOWED_STRATEGIES, get_checker # ✨ added from collections import deque @@ -42,6 +42,20 @@ _HAS_TESSERACT = False +# -------------------- error helpers -------------------- +def error_response(status_code: int, code: str, message: str, errors: Optional[list] = None): + errs = errors or [{"code": code, "message": message}] + payload = { + "ok": False, + "error": True, + "has_errors": len(errs) > 0, + "code": code, + "detail": message, + "errors": errs, + } + return JSONResponse(payload, status_code=status_code) + + # -------------------- utility -------------------- def _safe_uid(s: str) -> str: """Make a filesystem-safe id (no spaces/odd chars).""" @@ -55,7 +69,14 @@ def _ocr_image_bytes(data: bytes) -> str: try: with Image.open(io.BytesIO(data)) as img: try: - return pytesseract.image_to_string(img) + # Normalize dark-theme screenshots for better OCR. + g = img.convert("L") + mean_luma = ImageStat.Stat(g).mean[0] if ImageStat else 128 + if mean_luma < 80: + g = ImageOps.invert(g) + g = ImageOps.autocontrast(g) + g = g.filter(ImageFilter.SHARPEN) + return pytesseract.image_to_string(g) except TesseractNotFoundError: return "" # no OCR available → behave gracefully except (UnidentifiedImageError, OSError): @@ -65,6 +86,7 @@ def _ocr_image_bytes(data: bytes) -> str: def _extract_pdf_bytes( data: bytes, previews_dir: Path, stem: str ) -> tuple[str, Optional[Path]]: + print("[extract_pdf_bytes] start", {"bytes": len(data), "stem": stem}) if not fitz: return "", None try: @@ -77,14 +99,19 @@ def _extract_pdf_bytes( preview_path = previews_dir / f"{stem}_page1.png" pix.save(str(preview_path)) doc.close() + print("[extract_pdf_bytes] done", {"chars": len(text), "preview": str(preview_path) if preview_path else None}) return text, preview_path except Exception: return "", None -def _extract_docx_bytes(data: bytes) -> str: +def _extract_docx_bytes(data: bytes, previews_dir: Path) -> tuple[str, Optional[Path]]: + """ + Extract text from DOCX, and OCR any embedded images as a fallback. + Returns (text, preview_path). + """ if not DocxDocument: - return "" + return "", None try: doc = DocxDocument(io.BytesIO(data)) parts: list[str] = [] @@ -96,9 +123,34 @@ def _extract_docx_bytes(data: bytes) -> str: for cell in row.cells: if cell.text: parts.append(cell.text) - return "\n".join(parts) + ocr_parts: list[str] = [] + preview_path: Optional[Path] = None + for rel in doc.part._rels.values(): + # Only process image relationships + reltype = getattr(rel, "reltype", "") or "" + if "image" not in reltype: + continue + target = getattr(rel, "target_part", None) + blob = getattr(target, "blob", None) + if not blob: + continue + # OCR the embedded image + ocr_text = _ocr_image_bytes(blob) + if ocr_text.strip(): + ocr_parts.append(ocr_text) + # Save first image as preview + if not preview_path: + previews_dir.mkdir(parents=True, exist_ok=True) + # rel.target_ref looks like 'media/image1.png' + fname = Path(getattr(target, "partname", "image")) + preview_path = previews_dir / _safe_uid(fname.name) + with open(preview_path, "wb") as f: + f.write(blob) + + full_text = "\n".join(t for t in ["\n".join(parts)] + ocr_parts if t) + return full_text, preview_path except Exception: - return "" + return "", None def extract_text_and_preview_bytes( @@ -115,7 +167,7 @@ def extract_text_and_preview_bytes( if ext == ".pdf": return _extract_pdf_bytes(data, previews_dir, Path(_safe_uid(filename)).stem) if ext == ".docx": - return _extract_docx_bytes(data), None + return _extract_docx_bytes(data, previews_dir) if ext in { ".txt", ".log", @@ -175,27 +227,43 @@ def index(): @app.get("/strategies") def api_strategies(): - out = [] - for s in load_strategies(): - try: - desc = s.description() - except Exception: - desc = "" - out.append({"name": s.name, "description": desc}) - return out + """Return only the curated benchmark strategies expected by the UI.""" + try: + # preserve canonical order from ALLOWED_STRATEGIES + return [ + {"name": name, "description": desc} + for name, desc in ALLOWED_STRATEGIES.items() + ] + except Exception as exc: + return error_response( + status_code=500, + code="STRATEGY_LOAD_FAILED", + message="Unable to load strategies.", + errors=[{"code": "STRATEGY_LOAD_FAILED", "message": str(exc)}], + ) @app.get("/health") def health(): """Quick status to debug issues without crashing the UI.""" - return { - "ok": True, - "has_tesseract": _HAS_TESSERACT, - "tesseract_version": _TESSERACT_VERSION, - "template_exists": (TEMPLATES / "report_template.docx").exists(), - "previews_dir": str(PREVIEWS), - "out_dir": str(OUT_DIR), - } + try: + return { + "ok": True, + "has_tesseract": _HAS_TESSERACT, + "tesseract_version": _TESSERACT_VERSION, + "template_exists": (TEMPLATES / "report_template.docx").exists(), + "previews_dir": str(PREVIEWS), + "out_dir": str(OUT_DIR), + "error": False, + "errors": [], + } + except Exception as exc: + return error_response( + status_code=500, + code="HEALTH_CHECK_FAILED", + message="Health check failed.", + errors=[{"code": "HEALTH_CHECK_FAILED", "message": str(exc)}], + ) # ✨ Recent scan: APIs + page + aliases @app.post("/api/scan-mem-log") @@ -253,33 +321,60 @@ def scan_log_redirect(): @app.post("/scan") async def scan( evidence: UploadFile = File(...), - strategy_name: str = Form(...), + strategy_name: str = Form(...), # preserved for frontend compatibility + strategy: str | None = Form(None), # alternative field name user_id: str = Form("user"), ): try: - # find strategy - strategy = next((s for s in load_strategies() if s.name == strategy_name), None) - if not strategy: - raise HTTPException(status_code=400, detail=f"Unknown strategy: {strategy_name}") + strategy_label = strategy or strategy_name + strat_obj = get_checker(strategy_label) if 'get_checker' in globals() else None + if strat_obj is None: + # fallback to previous lookup + strat_obj = next((s for s in load_strategies() if s.name == strategy_label), None) + + if not strat_obj: + _push_mem_log(user_id, strategy_label, "error") + return error_response( + status_code=400, + code="STRATEGY_NOT_FOUND", + message=f"Unknown strategy: {strategy_label}", + ) # read upload content = await evidence.read() if not content: - raise HTTPException(status_code=400, detail="Empty upload") + _push_mem_log(user_id, strat_obj.name, "error") + return error_response( + status_code=400, + code="EMPTY_UPLOAD", + message="Evidence file is empty.", + ) # extract text + preview text, preview_path = extract_text_and_preview_bytes( evidence.filename, content, PREVIEWS ) if not text.strip(): - _push_mem_log(user_id, strategy.name, "success") # ✨ log a successful run even without readable text + _push_mem_log(user_id, strat_obj.name, "success") # log a successful run even without readable text return JSONResponse( - {"ok": True, "findings": [], "reports": [], "note": "No readable text found in evidence."} + { + "ok": True, + "error": False, + "has_errors": False, + "errors": [], + "findings": [], + "reports": [], + "note": "No readable text found in evidence." + } ) # run rules - if hasattr(strategy, "emit_hits"): - findings = strategy.emit_hits(text, source_file=evidence.filename) or [] + if hasattr(strat_obj, "emit_hits"): + try: + findings = strat_obj.emit_hits(text, source_file=evidence.filename, user_id=user_id) or [] + except TypeError: + # fallback for strategies that don't accept extra kwargs + findings = strat_obj.emit_hits(text) or [] else: hits = strategy.match(text) or [] findings = ( @@ -295,13 +390,59 @@ async def scan( if hits else [] ) + # Normalize findings to a common schema and ensure details are present + def _clip(s: str, n: int = 400) -> str: + if not s: + return "" + s = " ".join(str(s).split()) + return s if len(s) <= n else s[: n - 3] + "..." + + normalized = [] + for f in findings: + f = dict(f) + f.setdefault("test_id", "") + f.setdefault("sub_strategy", "") + f.setdefault("detected_level", "") + f.setdefault("pass_fail", "UNKNOWN") + f.setdefault("priority", "") + f.setdefault("recommendation", "") + f.setdefault("evidence", []) + + # coerce evidence to list[str] + ev = f.get("evidence") + if isinstance(ev, str): + ev_list = [ev] + elif isinstance(ev, list): + ev_list = [str(x) for x in ev] + else: + ev_list = [] + f["evidence"] = ev_list + + # details (observed/expected) + obs_full = "" + if ev_list: + obs_full = ev_list[0] + exp_full = f.get("recommendation") or f.get("description") or "" + + det = f.get("details") or {} + det.setdefault("observed_full", obs_full) + det.setdefault("expected_full", exp_full) + det.setdefault("observed", _clip(det.get("observed_full", ""))) + det.setdefault("expected", _clip(det.get("expected_full", ""))) + f["details"] = det + + normalized.append(f) + + findings = normalized + # generate reports (PDF preferred; fallback DOCX/TXT so we never 500) OUT_DIR.mkdir(parents=True, exist_ok=True) generated: list[str] = [] note = "" + errors: list[dict] = [] for r in findings: - base_uid = _safe_uid(f"{user_id}-{strategy.name}-{Path(evidence.filename).stem}") + base_uid = _safe_uid(f"{user_id}-{strat_obj.name}-{Path(evidence.filename).stem}") # make name unique to avoid 'Permission denied' if file is locked from previous run uid = f"{base_uid}-{int(time.time())}" @@ -311,7 +452,7 @@ async def scan( "UserID": user_id, "Evidence": evidence.filename, "Evidence Preview": str(preview_path) if preview_path else "", - "Strategy": strategy.name, + "Strategy": strat_obj.name, "TestID": r.get("test_id", ""), "Sub-Strategy": r.get("sub_strategy", ""), "ML Level": r.get("detected_level", ""), @@ -331,46 +472,36 @@ async def scan( base_dir=str(ROOT), ) generated.append(Path(pdf_path).name) - except Exception: - # Fallback: create a simple DOCX (or TXT) so the UI still returns a download - note = "PDF converter not available or file was locked; generated a DOCX/TXT fallback." - fallback_name = f"{uid}.docx" if DocxDocument else f"{uid}.txt" - fallback_path = OUT_DIR / fallback_name - - try: - if DocxDocument: - doc = DocxDocument() - doc.add_heading("AutoAudit – Finding", 0) - doc.add_paragraph(f"Strategy: {strategy.name}") - doc.add_paragraph(f"Test ID: {payload['TestID']}") - doc.add_paragraph(f"Pass/Fail: {payload['Pass/Fail']}") - if payload["Recommendation"]: - doc.add_paragraph(f"Recommendation: {payload['Recommendation']}") - if payload["Evidence Extract"]: - doc.add_paragraph("Evidence:") - doc.add_paragraph(payload["Evidence Extract"]) - doc.save(str(fallback_path)) - else: - with open(fallback_path, "w", encoding="utf-8") as f: - f.write(f"Strategy: {strategy.name}\n") - f.write(f"Test ID: {payload['TestID']}\n") - f.write(f"Pass/Fail: {payload['Pass/Fail']}\n") - f.write(f"Recommendation: {payload['Recommendation']}\n") - f.write(f"Evidence: {payload['Evidence Extract']}\n") - generated.append(fallback_path.name) - except Exception: - # If even the fallback fails, keep going without a report link - pass - - _push_mem_log(user_id, strategy.name, "success") # ✨ log success - return {"ok": True, "findings": findings, "reports": generated, "note": note} - - except HTTPException: + except Exception as exc: + # If report generation fails even with fallbacks, capture the error + errors.append({"code": "REPORT_GENERATION_FAILED", "message": str(exc)}) + + _push_mem_log(user_id, strat_obj.name, "success") # ✨ log success + return { + "ok": True, + "error": False, + "has_errors": len(errors) > 0, + "errors": errors, + "findings": findings, + "reports": generated, + "note": note, + } + + except HTTPException as exc: _push_mem_log(user_id if 'user_id' in locals() else "user", strategy_name, "error") # ✨ log error - raise - except Exception: + return error_response( + status_code=exc.status_code, + code="VALIDATION_ERROR", + message=str(exc.detail), + ) + except Exception as exc: _push_mem_log(user_id if 'user_id' in locals() else "user", strategy_name, "error") # ✨ log error - raise + return error_response( + status_code=500, + code="SERVER_ERROR", + message="Unexpected error during scan.", + errors=[{"code": "SERVER_ERROR", "message": str(exc)}], + ) @app.get("/reports/{filename}") diff --git a/security/reports/report_service.py b/security/reports/report_service.py index 9d0e904d..a2e32a99 100644 --- a/security/reports/report_service.py +++ b/security/reports/report_service.py @@ -9,6 +9,7 @@ from docx import Document from docx.shared import Inches +from fpdf import FPDF def generate_pdf( @@ -277,10 +278,59 @@ def _convert_docx_to_pdf(input_docx: Path, output_pdf: Path) -> None: expected = output_pdf.with_suffix(".pdf") if expected.exists() and expected != output_pdf: expected.replace(output_pdf) - except Exception as e: - raise RuntimeError( - "PDF conversion failed. Install Microsoft Word for docx2pdf or LibreOffice for fallback." - ) from e + return + except Exception: + pass + + # Final fallback: generate a simple text PDF (no external binaries) + if _simple_pdf_from_docx(input_docx, output_pdf): + return + + raise RuntimeError( + "PDF conversion failed. Install Microsoft Word for docx2pdf or LibreOffice, or ensure fpdf2 fallback works." + ) + + +def _simple_pdf_from_docx(input_docx: Path, output_pdf: Path) -> bool: + """ + Pure-Python fallback using fpdf2 to ensure a downloadable PDF is always produced. + """ + try: + doc = Document(str(input_docx)) + pdf = FPDF() + pdf.set_auto_page_break(auto=True, margin=15) + pdf.add_page() + pdf.set_font("Helvetica", size=12) + + def _safe_text(text: str) -> str: + """fpdf core fonts are latin-1; strip/replace unsupported chars.""" + if text is None: + return "" + try: + return text.encode("latin-1").decode("latin-1") + except Exception: + return text.encode("ascii", "replace").decode("ascii") + + def _write_line(line: str): + line = _safe_text(line) + if not line: + return + pdf.multi_cell(0, 8, line) + pdf.ln(1) + + for p in doc.paragraphs: + text = (p.text or "").strip() + _write_line(text) + + for tbl in doc.tables: + for row in tbl.rows: + row_text = " | ".join((cell.text or "").strip() for cell in row.cells) + _write_line(row_text) + + pdf.output(str(output_pdf)) + return True + except Exception: + return False def _remove_markers_everywhere(doc, markers: list[str]) -> None: for p in _iter_paragraphs(doc): diff --git a/security/requirements.txt b/security/requirements.txt index aa7d372c..3a3ed5c7 100644 --- a/security/requirements.txt +++ b/security/requirements.txt @@ -8,4 +8,5 @@ docx2pdf pymupdf python-multipart uvicorn[standard] -fastapi \ No newline at end of file +fastapi +fpdf2 diff --git a/security/strategies/__init__.py b/security/strategies/__init__.py index aaac2fa9..4541f401 100644 --- a/security/strategies/__init__.py +++ b/security/strategies/__init__.py @@ -1,26 +1,14 @@ -import pkgutil, importlib, inspect -from .overview import Strategy +from .custom_benchmarks import get_strategy, STRATEGY_DEFS -def load_strategies(): - strategies = [] - pkg = __name__ +# Canonical set of strategies the UI should see (order preserved) +ALLOWED_STRATEGIES = {entry["name"]: entry["description"] for entry in STRATEGY_DEFS} - # iterate over all modules in strategies - for _, modname, ispkg in pkgutil.iter_modules(__path__): - if ispkg or modname == "overview": - continue - module = importlib.import_module(f"{pkg}.{modname}") - # prefer get_strategy() if present - if hasattr(module, "get_strategy"): - strategies.append(module.get_strategy()) - continue +def load_strategies(): + """Return the curated strategies with embedded rule logic.""" + return list(get_strategy()) - # otherwise find subclasses of Strategy - for _, obj in inspect.getmembers(module, inspect.isclass): - if issubclass(obj, Strategy) and obj is not Strategy: - strategies.append(obj()) - # sort alphabetically - strategies.sort(key=lambda s: s.name.lower()) - return strategies +def get_checker(strategy_name: str): + """Return the strategy object by name (or None).""" + return next((s for s in load_strategies() if s.name == strategy_name), None) diff --git a/security/strategies/application_control.py b/security/strategies/application_control.py index 5c8c32d6..8c40ba2b 100644 --- a/security/strategies/application_control.py +++ b/security/strategies/application_control.py @@ -78,24 +78,69 @@ def _ml_level_from_test_id(test_id: str) -> int | None: # main detection logic def emit_hits(self, raw_text: str, source_file: str = "", **kwargs): t = self.normalize(raw_text) + raw = raw_text or "" + + def _clean(s: str) -> str: + return " ".join(s.split()) + + def _snippet_from_regex(patterns, text, context=120, max_len=400): + for pat in patterns: + m = re.search(pat, text, re.IGNORECASE | re.MULTILINE) + if m: + start = max(0, m.start() - context) + end = min(len(text), m.end() + context) + snip = _clean(text[start:end].strip()) + if len(snip) > max_len: + snip = snip[: max_len - 3] + "..." + prefix = "..." if start > 0 else "" + suffix = "..." if end < len(text) else "" + return f"{prefix}{snip}{suffix}" + return None + + def _snippet_from_phrase(phrases, text, context=120, max_len=400): + for phrase in phrases: + if not phrase: + continue + m = re.search(re.escape(phrase), text, re.IGNORECASE) + if m: + start = max(0, m.start() - context) + end = min(len(text), m.end() + context) + snip = _clean(text[start:end].strip()) + if len(snip) > max_len: + snip = snip[: max_len - 3] + "..." + prefix = "..." if start > 0 else "" + suffix = "..." if end < len(text) else "" + return f"{prefix}{snip}{suffix}" + return None # avoids false positives like blocklist, unblocked etc. - enforce_hit = self._any_regex(t, [r"\benforc(?:e|ed|ing)\b", r"\brules are enforced\b", r"\benforce rules\b"]) - block_hit = self._any_regex(t, [r"\bblock(?:ed|ing)?\b"]) or self._any_regex(t, self.BLOCK_REGEX) + enforce_snip = _snippet_from_regex([r"\benforc(?:e|ed|ing)\b", r"\brules are enforced\b", r"\benforce rules\b"], raw) + block_snip = _snippet_from_regex([r"\bblock(?:ed|ing)?\b"], raw) or _snippet_from_regex(self.BLOCK_REGEX, raw) rows = [] # detection criteria i.e. require (enforce|block) AND (label OR extension) for test_id, sub, priority, recommendation, label_key in self.RULES: label_hit = self._any_substr(t, self.LABELS[label_key]) + label_snip = _snippet_from_phrase(self.LABELS[label_key], raw) + ext_hit = self._any_regex(t, self.EXT_PATTERNS_BY_LABEL[label_key]) + ext_snip = _snippet_from_regex(self.EXT_PATTERNS_BY_LABEL[label_key], raw) - action_ok = bool(enforce_hit or block_hit) - evidence_ok = bool(label_hit or ext_hit) + action_ok = bool(enforce_snip or block_snip) + evidence_ok = bool(label_snip or ext_snip) # if both are detected = pass output if action_ok and evidence_ok: - evidence = [x for x in (label_hit, ext_hit, enforce_hit, block_hit) if x] + evidence = [] + if label_snip: + evidence.append(f"Rule mentions: {label_snip}") + if ext_snip: + evidence.append(f"File types referenced: {ext_snip}") + if enforce_snip: + evidence.append(f"Enforcement text: {enforce_snip}") + if block_snip: + evidence.append(f"Blocking text: {block_snip}") rows.append({ "test_id": test_id, "sub_strategy": sub, @@ -109,7 +154,7 @@ def emit_hits(self, raw_text: str, source_file: str = "", **kwargs): if rows: return rows - # generic fail output + # generic fail output with a concise reason return [{ "test_id": "ML1-AC-01 to ML1-AC-07", # total of all test_ids "sub_strategy": "", @@ -121,7 +166,7 @@ def emit_hits(self, raw_text: str, source_file: str = "", **kwargs): "from running in user or temporary folders. " "(2) Ensure rules are enforced on all workstations and reviewed periodically (ideally once per quarter)." ), - "evidence": [] + "evidence": ["No blocking/enforcement text or file-type rules found in the evidence file."] }] # iterate each test_id @@ -131,4 +176,4 @@ def match(self, raw_text: str): if r.get("pass_fail") == "Pass": lvl = r.get("detected_level") outs.append(f"{r['test_id']} (ML{lvl}, {r['priority']}): {r['sub_strategy']} rules have been enforced") - return outs \ No newline at end of file + return outs diff --git a/security/strategies/configure_macro_settings.py b/security/strategies/configure_macro_settings.py index 64f02c11..31520312 100644 --- a/security/strategies/configure_macro_settings.py +++ b/security/strategies/configure_macro_settings.py @@ -188,4 +188,28 @@ def is_adgroup_side() -> bool: ev(t), )) - return rows \ No newline at end of file + # If nothing matched, emit a concise fail so the UI shows a row instead of "no findings" + if not rows: + rows.append(self._row( + "ML1-OM-00", + "Macro evidence missing", + "ML1", + "FAIL", + "Medium", + "Provide a screenshot or text from Office Trust Center / GPO showing macro settings (e.g., 'Disable all macros without notification', blockcontentexecutionfromInternet=1).", + ev(t[:400] or "No readable text extracted"), + )) + rows[-1]["details"] = { + "observed": self._clip(t or "No macro-related content detected."), + "expected": "Evidence that macros are disabled/blocked via Trust Center or GPO (e.g., 'Disable all macros without notification', blockcontentexecutionfromInternet=1).", + } + + # Add observed/expected for existing rows when helpful + for r in rows: + if "details" not in r: + r["details"] = { + "observed": self._clip(" ".join(r.get("evidence", []))) if r.get("evidence") else self._clip(t), + "expected": r.get("recommendation", ""), + } + + return rows diff --git a/security/strategies/custom_benchmarks.py b/security/strategies/custom_benchmarks.py new file mode 100644 index 00000000..9a8329be --- /dev/null +++ b/security/strategies/custom_benchmarks.py @@ -0,0 +1,204 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import List, Sequence, Tuple +import re + +from .overview import Strategy + + +def _clean_line(text: str) -> str: + line = text.strip().lstrip("#*") + line = re.sub(r"\s*\.\s*", ".", line) + line = re.sub(r"\s*=\s*", "=", line) + line = re.sub(r"\s+", " ", line) + return line + + +def _extract_evidence_snippets(text: str, max_lines: int = 2) -> List[str]: + lines = [_clean_line(ln) for ln in text.splitlines() if ln.strip()] + if not lines: + return [] + return lines[:max_lines] + + +@dataclass +class StrategyMeta: + name: str + description: str + category: str = "" + severity: str = "" + evidence_types: Sequence[str] | None = None + + +class BaseStrategyChecker: + """Lightweight heuristic checker base class using keyword rules.""" + + # RULES: (test_id, sub_strategy, level, priority, recommendation, risk_keywords) + RULES: Sequence[Tuple[str, str, str, str, str, Sequence[str]]] = () + + def __init__(self, metadata: StrategyMeta): + self.metadata = metadata + + def run_checks( + self, + extracted_text: str, + filename: str | None = None, + user_id: str | None = None, + ) -> List[dict]: + if not extracted_text: + return [] + + text_lower = extracted_text.lower() + evidence_snippets = _extract_evidence_snippets(extracted_text) + if not evidence_snippets: + evidence_snippets = ["Readable text present, but no specific lines were captured."] + + findings: List[dict] = [] + for test_id, sub, level, priority, recommendation, risk_keywords in self.RULES: + hit = any(k in text_lower for k in risk_keywords) if risk_keywords else False + pass_fail = "FAIL" if hit else "PASS" + findings.append({ + "test_id": test_id, + "sub_strategy": sub, + "detected_level": level, + "pass_fail": pass_fail, + "priority": priority, + "recommendation": recommendation, + "evidence": evidence_snippets, + "description": self._description(hit, filename, user_id, sub), + "confidence": "0.82" if hit else "0.65", + }) + + return findings + + def _description(self, risk_detected: bool, filename: str | None, user_id: str | None, sub: str) -> str: + base = f"File: {filename or 'unknown'} | Check: {sub}" + if user_id: + base += f" | User: {user_id}" + base += " | Risk" if risk_detected else " | No high-risk indicators" + return base + + +class CISMicrosoft365Checker(BaseStrategyChecker): + RULES = ( + ("CIS-001", "Identity & Access", "High", "Critical", "Enable MFA for all admins; block legacy auth.", ("mfa disabled", "legacy auth", "basic auth", "no mfa")), + ("CIS-002", "Data Protection", "Medium", "High", "Implement DLP and encryption for sensitive data.", ("no dlp", "dlp not", "unencrypted", "public link")), + ("CIS-003", "Access Management", "Low", "Medium", "Review RBAC/least privilege and access reviews.", ("excessive permissions", "global admin", "no access review", "everyone")), + ("CIS-004", "Audit Logging", "Medium", "Medium", "Extend audit log retention and enable mailbox auditing.", ("audit log", "90 days", "logging disabled", "audit disabled")), + ("CIS-005", "Email Security", "High", "Critical", "Enable ATP/Defender, anti-phishing, and safe links/attachments.", ("atp not", "safe links off", "phishing", "spoof")), + ) + + +class NISTComplianceChecker(BaseStrategyChecker): + RULES = ( + ("NIST-IR", "Incident Response", "High", "High", "Document and test incident response playbooks.", ("no incident response", "missing ir", "untested ir")), + ("NIST-PF", "Protect", "Medium", "Medium", "Improve access control and least privilege.", ("unauthorized access", "shared account", "no mfa")), + ("NIST-DE", "Detect", "Medium", "Medium", "Enable monitoring and alerting for key assets.", ("no monitoring", "logging disabled", "no siem")), + ("NIST-RS", "Recover", "Low", "Low", "Test backups and recovery procedures.", ("no backup", "restore failed", "untested backup")), + ) + + +class ISO27001Checker(BaseStrategyChecker): + RULES = ( + ("ISO-A.5", "Policies & ISMS", "High", "High", "Maintain current ISMS policies and governance.", ("no policy", "outdated policy", "isms missing")), + ("ISO-A.8", "Asset Management", "Medium", "Medium", "Keep complete asset inventory and classification.", ("no asset inventory", "unknown assets", "shadow it")), + ("ISO-A.9", "Access Control", "Medium", "High", "Tighten access control and review rights.", ("weak access control", "shared account", "no mfa")), + ("ISO-A.12", "Operations Security", "Low", "Medium", "Harden ops: patching, malware protection, logging.", ("no patch", "malware", "logging disabled")), + ) + + +class SOC2ReadinessChecker(BaseStrategyChecker): + RULES = ( + ("SOC2-CC", "Common Criteria", "High", "High", "Enforce access control and MFA across services.", ("no mfa", "shared account", "weak password")), + ("SOC2-LOG", "Logging & Monitoring", "Medium", "Medium", "Enable centralized logging and alerting.", ("no logging", "missing monitoring", "log retention 7")), + ("SOC2-AV", "Availability", "Medium", "Medium", "Document DR/BCP and test failover.", ("no dr", "no bcp", "no failover")), + ("SOC2-CHG", "Change Management", "Low", "Low", "Enforce change reviews and approvals.", ("no change management", "no cab", "unauthorized change")), + ) + + +class GDPRComplianceChecker(BaseStrategyChecker): + RULES = ( + ("GDPR-ROPA", "Records of Processing", "Medium", "Medium", "Maintain ROPA and DPIA for high-risk processing.", ("no ropa", "no dpia", "missing dpia")), + ("GDPR-DSR", "Data Subject Rights", "High", "High", "Implement SAR/erasure/rectification workflows.", ("subject access", "sar", "erasure", "no dsr")), + ("GDPR-RET", "Retention", "Medium", "Medium", "Apply retention limits and deletion schedules.", ("retention violation", "keep forever", "no retention")), + ("GDPR-DPA", "Processors", "High", "High", "Put DPAs in place with processors and sub-processors.", ("no dpa", "missing dpa", "processor")), + ("GDPR-SEC", "Security", "Medium", "High", "Encrypt personal data and restrict access.", ("unencrypted personal data", "pii exposed", "open s3")), + ) + + +class CheckerStrategy(Strategy): + """Adapter to make checkers look like Strategy objects expected by the scanner/UI.""" + + def __init__(self, metadata: StrategyMeta, checker_cls: type[BaseStrategyChecker]): + self.meta = metadata + self.checker_cls = checker_cls + self.name = metadata.name + self.id = "BM" + + def description(self) -> str: + return self.meta.description + + def emit_hits(self, raw_text: str, source_file: str | None = None, user_id: str | None = None, **kwargs): + checker = self.checker_cls(self.meta) + return checker.run_checks(raw_text, filename=source_file, user_id=user_id) + + +# Registry of strategy definitions +STRATEGY_DEFS = [ + { + "name": "CIS Microsoft 365 Audit", + "description": "Comprehensive Microsoft 365 security assessment based on CIS benchmarks", + "category": "Cloud Security", + "severity": "High", + "evidence_types": ["pdf", "png", "jpg", "jpeg", "tif", "tiff", "bmp", "webp", "txt", "docx", "log", "csv"], + "checker": CISMicrosoft365Checker, + }, + { + "name": "NIST Compliance Check", + "description": "NIST Cybersecurity Framework control verification.", + "category": "Framework Alignment", + "severity": "Medium", + "evidence_types": ["pdf", "png", "jpg", "jpeg", "tif", "tiff", "bmp", "webp", "txt", "docx", "log", "csv"], + "checker": NISTComplianceChecker, + }, + { + "name": "ISO 27001 Assessment", + "description": "ISO 27001 ISMS and Annex A control assessment.", + "category": "Governance", + "severity": "High", + "evidence_types": ["pdf", "png", "jpg", "jpeg", "tif", "tiff", "bmp", "webp", "txt", "docx", "log", "csv"], + "checker": ISO27001Checker, + }, + { + "name": "SOC 2 Readiness", + "description": "SOC 2 Type II gap analysis against Trust Services Criteria.", + "category": "Assurance", + "severity": "Medium", + "evidence_types": ["pdf", "png", "jpg", "jpeg", "tif", "tiff", "bmp", "webp", "txt", "docx", "log", "csv"], + "checker": SOC2ReadinessChecker, + }, + { + "name": "GDPR Compliance Scan", + "description": "Assessment of privacy and data protection controls for GDPR readiness.", + "category": "Privacy", + "severity": "High", + "evidence_types": ["pdf", "png", "jpg", "jpeg", "tif", "tiff", "bmp", "webp", "txt", "docx", "log", "csv"], + "checker": GDPRComplianceChecker, + }, +] + + +def get_strategy(): + """Return Strategy objects consumed by the scanner / UI.""" + strategies: list[Strategy] = [] + for entry in STRATEGY_DEFS: + meta = StrategyMeta( + name=entry["name"], + description=entry["description"], + category=entry.get("category", ""), + severity=entry.get("severity", ""), + evidence_types=entry.get("evidence_types", ()), + ) + strategies.append(CheckerStrategy(meta, entry["checker"])) + return strategies diff --git a/security/strategies/multi-factor_authentication.py b/security/strategies/multi-factor_authentication.py index 12630abc..da7bdffd 100644 --- a/security/strategies/multi-factor_authentication.py +++ b/security/strategies/multi-factor_authentication.py @@ -15,6 +15,20 @@ def emit_hits(self, text: str, source_file: str = "") -> list[dict]: findings = [] lowered = text.lower() + # Keep evidence excerpts short for UI readability + def _excerpt(t: str, max_chars: int = 400, max_lines: int = 6, keywords=None) -> str: + lines = t.splitlines() + if keywords: + kw_lines = [ln for ln in lines if any(k in ln for k in keywords)] + if kw_lines: + lines = kw_lines + if len(lines) > max_lines: + lines = lines[:max_lines] + clip = "\n".join(lines) + if len(clip) > max_chars: + clip = clip[: max_chars - 3] + "..." + return clip + # --- Indicators --- disabled = ["mfa disabled", "not configured", "disabled for all users"] admins_only = ["mfa required for admins", "privileged roles only"] @@ -36,7 +50,7 @@ def emit_hits(self, text: str, source_file: str = "") -> list[dict]: "sub_strategy": "MFA status", "severity": "high", "description": "MFA appears disabled or legacy authentication is allowed.", - "evidence": f"[DEBUG OCR from {source_file}]\n{lowered}", + "evidence": _excerpt(f"[OCR from {source_file}]\n{lowered}", keywords=disabled + legacy_auth), "detected_level": 0 }) @@ -46,7 +60,7 @@ def emit_hits(self, text: str, source_file: str = "") -> list[dict]: "sub_strategy": "Scope", "severity": "medium", "description": "MFA is enforced for admins only.", - "evidence": f"[DEBUG OCR from {source_file}]\n{lowered}", + "evidence": _excerpt(f"[OCR from {source_file}]\n{lowered}", keywords=admins_only), "detected_level": 1 }) @@ -56,7 +70,7 @@ def emit_hits(self, text: str, source_file: str = "") -> list[dict]: "sub_strategy": "All users (weak)", "severity": "low", "description": "MFA is enforced for all users, but weak methods are allowed (SMS, app passwords).", - "evidence": f"[DEBUG OCR from {source_file}]\n{lowered}", + "evidence": _excerpt(f"[OCR from {source_file}]\n{lowered}", keywords=all_users + weak_methods), "detected_level": 2 }) @@ -66,7 +80,7 @@ def emit_hits(self, text: str, source_file: str = "") -> list[dict]: "sub_strategy": "Strong MFA", "severity": "info", "description": "MFA enforced for all users with strong methods and legacy auth blocked.", - "evidence": f"[DEBUG OCR from {source_file}]\n{lowered}", + "evidence": _excerpt(f"[OCR from {source_file}]\n{lowered}", keywords=all_users + strong_methods), "detected_level": 3 }) diff --git a/tmp_test_pkg/pkg/__init__.py b/tmp_test_pkg/pkg/__init__.py new file mode 100644 index 00000000..afd1a026 --- /dev/null +++ b/tmp_test_pkg/pkg/__init__.py @@ -0,0 +1 @@ +x=1 \ No newline at end of file From a53613d4c932fcc40c4c2a6fdc0e959db2b011e2 Mon Sep 17 00:00:00 2001 From: NYASWA1014 Date: Sun, 7 Dec 2025 01:53:17 +1100 Subject: [PATCH 007/111] Improve LoginPage UI with validation and helper text --- frontend/src/pages/Auth/LoginPage.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/Auth/LoginPage.js b/frontend/src/pages/Auth/LoginPage.js index a9cee6a4..177e81de 100644 --- a/frontend/src/pages/Auth/LoginPage.js +++ b/frontend/src/pages/Auth/LoginPage.js @@ -6,11 +6,19 @@ export default function LoginPage({ onLogin, onSignUpClick }) { const [password, setPassword] = useState(''); const [showPassword, setShowPassword] = useState(false); const [rememberMe, setRememberMe] = useState(false); + const [error, setError] = useState(''); const handleSubmit = () => { + if (!email.trim() || !password.trim()) { + setError('Please enter both your email address and password to continue.'); + return; + } + + setError(''); onLogin(); }; + return (
- -
-
- - -
- Forgot password? -
- - - - -
- Or continue with -
- - - - - -
-
🛡️
-
-

Secure Enterprise Access

-

Your connection is encrypted and monitored for compliance with enterprise security standards

-
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/src/pages/Contact/ContactPage.js b/frontend/src/pages/Contact/ContactPage.js index 6bb840e9..66fc7bf6 100644 --- a/frontend/src/pages/Contact/ContactPage.js +++ b/frontend/src/pages/Contact/ContactPage.js @@ -30,7 +30,7 @@ const ContactPage = ({ onSignIn }) => { return (
- +
diff --git a/frontend/src/pages/Landing/AboutUs.css b/frontend/src/pages/Landing/AboutUs.css index 252a8225..2d6f297b 100644 --- a/frontend/src/pages/Landing/AboutUs.css +++ b/frontend/src/pages/Landing/AboutUs.css @@ -1,283 +1,251 @@ -.about-container { +.about-page { + background: #0a1628; + color: #ffffff; + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; min-height: 100vh; - background: - radial-gradient(900px 600px at 10% 0%, #1f6e7f 0%, rgba(31,110,127,0) 55%), - radial-gradient(800px 500px at 95% 100%, #111827 0%, rgba(17,24,39,0) 60%), - linear-gradient(180deg, #14364a 0%, #0a1622 100%); - color: #e9f0f5; - font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; } -.about-content { - max-width: 1000px; - margin: 0 auto; - padding: 0 40px 80px; +.about-page main { + display: flex; + flex-direction: column; + gap: 3rem; } .about-hero { + background: linear-gradient(135deg, #0a1628 0%, #1a3a4f 50%, #2a4a5f 100%); + padding: 6rem 5% 4rem; text-align: center; - padding: 80px 0 60px; + position: relative; + overflow: hidden; +} + +.about-hero::before { + content: ""; + position: absolute; + width: 520px; + height: 520px; + border-radius: 50%; + background: radial-gradient(circle, rgba(64, 224, 208, 0.1) 0%, transparent 70%); + top: -180px; + right: -140px; + animation: aboutPulse 8s ease-in-out infinite; +} + +@keyframes aboutPulse { + 0%, + 100% { + transform: scale(1); + opacity: 0.5; + } + 50% { + transform: scale(1.2); + opacity: 0.8; + } } -.about-logo { - width: 180px; - height: auto; - border-radius: 15px; - box-shadow: 0 0 20px rgba(40, 224, 214, 0.3); +.hero-logo { + width: 220px; + margin: 0 auto 2rem; display: block; - margin: 0 auto 30px; + filter: drop-shadow(0 10px 25px rgba(64, 224, 208, 0.35)); } -.about-hero h2 { - font-size: 42px; - font-weight: 800; - margin: 0 0 16px 0; - background: linear-gradient(135deg, #28e0d6, #1fc9c0); +.about-hero h1 { + font-size: clamp(2.5rem, 6vw, 3.4rem); + margin-bottom: 1rem; + background: linear-gradient(135deg, #ffffff, #40e0d0); -webkit-background-clip: text; -webkit-text-fill-color: transparent; - background-clip: text; } -.about-subtitle { - font-size: 20px; - color: #b9c5d3; - max-width: 600px; +.about-hero p { + font-size: 1.2rem; + color: #b0c4de; + max-width: 760px; margin: 0 auto; - line-height: 1.6; } -.about-section { - margin-bottom: 60px; - padding: 40px; - background: - radial-gradient(120% 120% at 10% -10%, rgba(42,215,209,0.08) 0%, rgba(8,20,30,0) 60%), - rgba(15, 35, 52, 0.7); - border: 1px solid rgba(120, 220, 255, 0.18); - border-radius: 16px; - box-shadow: - 0 10px 30px rgba(0,0,0,.35), - inset 0 0 60px rgba(255,255,255,.03); - backdrop-filter: blur(6px); +.section { + padding: 0 5%; } -.about-section h2 { - font-size: 28px; - font-weight: 700; - margin: 0 0 20px 0; - color: #28e0d6; +.section-title { + text-align: center; + font-size: clamp(2rem, 5vw, 2.6rem); + margin-bottom: 2rem; + color: #40e0d0; } -.about-section#mission h2, -.about-section#features h2, -.about-section#standards h2 { - text-align: center; +.section-content { + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(64, 224, 208, 0.1); + border-radius: 20px; + padding: 3rem; + max-width: 1200px; + margin: 0 auto; + backdrop-filter: blur(10px); } -.about-section p { - font-size: 16px; - line-height: 1.7; - color: #b9c5d3; - margin-bottom: 16px; +.section-content p { + color: #b0c4de; + line-height: 1.8; + font-size: 1.05rem; } -.features-overview { +.features-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: 30px; - margin-top: 30px; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 2rem; + max-width: 1200px; + margin: 0 auto; } -.feature-item { - display: flex; - align-items: flex-start; - gap: 16px; +.feature-card { + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(64, 224, 208, 0.1); + border-radius: 20px; + padding: 2rem; + position: relative; + transition: all 0.3s ease; + overflow: hidden; } -.feature-icon { - width: 44px; - height: 44px; - display: grid; - place-items: center; - border-radius: 12px; - color: #2ad7d1; - background: radial-gradient(120% 120% at 0% 0%, rgba(42,215,209,.25), rgba(42,215,209,0) 60%); - filter: drop-shadow(0 4px 16px rgba(42,215,209,.28)); - flex-shrink: 0; +.feature-card::before { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient( + 135deg, + rgba(64, 224, 208, 0.08), + rgba(30, 144, 255, 0.08) + ); + opacity: 0; + transition: opacity 0.3s ease; } -.feature-item h3 { - font-size: 18px; - font-weight: 600; - margin: 0 0 8px 0; - color: #e9f0f5; +.feature-card:hover { + transform: translateY(-10px); + border-color: #40e0d0; + box-shadow: 0 20px 40px rgba(64, 224, 208, 0.2); } -.feature-item p { - font-size: 14px; - color: #94a3b8; - margin: 0; +.feature-card:hover::before { + opacity: 1; } -.about-feature-grid { - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); +.feature-icon { + width: 60px; + height: 60px; + border-radius: 15px; + background: linear-gradient(135deg, #40e0d0, #1e90ff); + display: flex; + align-items: center; + justify-content: center; + font-size: 1.8rem; + margin-bottom: 1.25rem; + position: relative; + z-index: 1; } -.about-feature-item { - background: rgba(13, 27, 42, 0.5); - padding: 20px; - border-radius: 14px; - border: 1px solid rgba(42, 215, 209, 0.2); - transition: transform 0.3s ease, border-color 0.3s ease; +.feature-card h3 { + font-size: 1.25rem; + margin-bottom: 0.75rem; + position: relative; + z-index: 1; } -.about-feature-item:hover { - transform: translateY(-4px); - border-color: rgba(42, 215, 209, 0.5); +.feature-card p { + color: #b0c4de; + line-height: 1.6; + position: relative; + z-index: 1; } .standards-grid { display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 24px; - margin-top: 30px; -} - -.standard-item { - background: rgba(15, 35, 52, 0.5); - padding: 24px; - border-radius: 12px; - border: 1px solid rgba(42, 215, 209, 0.2); - transition: transform 0.3s ease, border-color 0.3s ease; -} - -.standard-item:hover { - transform: translateY(-4px); - border-color: rgba(42, 215, 209, 0.4); -} - -.standard-item h4 { - font-size: 16px; - font-weight: 600; - margin: 0 0 8px 0; - color: #28e0d6; -} - -.standard-item p { - font-size: 14px; - color: #94a3b8; - margin: 0; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 2rem; + max-width: 1200px; + margin: 0 auto; } -.about-why { - text-align: center; +.standard-card { + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(64, 224, 208, 0.1); + border-radius: 16px; + padding: 2rem; + transition: all 0.3s ease; } -.about-why p { - max-width: 720px; - margin-left: auto; - margin-right: auto; +.standard-card:hover { + border-color: #40e0d0; + transform: translateY(-6px); } -.about-commitment { - text-align: center; +.standard-card h3 { + font-size: 1.2rem; + color: #40e0d0; + margin-bottom: 0.6rem; } -.about-commitment p { - max-width: 760px; - margin-left: auto; - margin-right: auto; +.standard-card p { + color: #b0c4de; + line-height: 1.6; + font-size: 0.95rem; } -.about-cta { +.cta-section { + background: linear-gradient(135deg, #1e3a5f, #0a1628); + border: 1px solid rgba(64, 224, 208, 0.2); + border-radius: 30px; + padding: 4rem 5%; + margin: 0 5% 4rem; text-align: center; - padding: 60px 40px; - background: - radial-gradient(120% 120% at 50% 50%, rgba(42,215,209,0.12) 0%, rgba(8,20,30,0) 70%), - rgba(15, 35, 52, 0.8); - border: 2px solid rgba(40, 224, 214, 0.3); - border-radius: 20px; - margin-top: 40px; } -.about-cta h2 { - font-size: 32px; - font-weight: 700; - margin: 0 0 16px 0; - color: #e9f0f5; +.cta-section h2 { + font-size: clamp(2rem, 5vw, 2.6rem); + margin-bottom: 1rem; } -.about-cta p { - font-size: 18px; - color: #b9c5d3; - margin: 0 0 30px 0; +.cta-section p { + color: #b0c4de; + font-size: 1.1rem; + margin-bottom: 2rem; } .cta-button { - background: #28e0d6; - color: #032224; border: none; - padding: 16px 32px; - border-radius: 10px; - font-size: 16px; - font-weight: 700; + border-radius: 999px; + padding: 0.9rem 2.5rem; + font-size: 1rem; + font-weight: 600; + background: linear-gradient(135deg, #40e0d0, #1e90ff); + color: #ffffff; cursor: pointer; - transition: all 0.3s ease; - box-shadow: 0 6px 20px rgba(40, 224, 214, 0.3); + transition: transform 0.2s ease, box-shadow 0.2s ease; } .cta-button:hover { - background: #1fc9c0; - transform: translateY(-2px); - box-shadow: 0 8px 25px rgba(40, 224, 214, 0.4); + transform: translateY(-3px); + box-shadow: 0 10px 25px rgba(64, 224, 208, 0.35); } @media (max-width: 768px) { - .about-header { - flex-direction: column; - gap: 1.5rem; - padding: 18px 20px; + .about-hero { + padding: 4rem 5% 3rem; } - .about-nav-links { - flex-wrap: wrap; - justify-content: center; + .section { + padding: 0 1.5rem; } - .about-content { - padding: 0 20px 60px; - } - - .about-hero h2 { - font-size: 36px; - } - - .about-subtitle { - font-size: 18px; + .section-content { + padding: 2rem; } - - .about-section { - padding: 30px 20px; - margin-bottom: 40px; - } - - .about-section h2 { - font-size: 24px; - } - - .features-overview { - grid-template-columns: 1fr; - gap: 20px; - } - - .standards-grid { - grid-template-columns: 1fr; - } - - .about-cta { - padding: 40px 20px; - } - - .about-cta h2 { - font-size: 26px; + + .cta-section { + margin: 0 1.5rem 3rem; + padding: 3rem 1.5rem; } } diff --git a/frontend/src/pages/Landing/AboutUs.js b/frontend/src/pages/Landing/AboutUs.js index 8f58e7c6..dbdc8db3 100644 --- a/frontend/src/pages/Landing/AboutUs.js +++ b/frontend/src/pages/Landing/AboutUs.js @@ -1,108 +1,149 @@ import React from "react"; import "./AboutUs.css"; import "./LandingPage.css"; -import { landingFeatures } from "./featuresData"; -import LandingFooter from "./components/LandingFooter"; import LandingHeader from "./components/LandingHeader"; +import LandingFooter from "./components/LandingFooter"; + +const features = [ + { + icon: "🔗", + title: "Microsoft 365 Integration", + description: + "Secure Graph API integration monitors MFA enforcement, audit logging, and conditional access policies in real-time.", + }, + { + icon: "📋", + title: "CIS Benchmark Compliance", + description: + "Automatically assess cloud configurations against CIS Microsoft 365 Foundations Benchmark and surface posture gaps.", + }, + { + icon: "⚡", + title: "Automated Scanning", + description: + "Continuous monitoring of security settings, sharing permissions, and policies catches issues before they escalate.", + }, + { + icon: "📊", + title: "Actionable Reports", + description: + "Generate audit-ready compliance reports with risk assessments and remediation guidance in minutes.", + }, + { + icon: "🛡️", + title: "Enterprise-Grade Security", + description: + "Bank-level encrypted data handling with a zero-knowledge architecture keeps your sensitive data in your control.", + }, + { + icon: "🚀", + title: "Fast & Automated", + description: + "Automated workflows reduce manual checks and cut audit preparation time by 80%.", + }, +]; -const AboutUs = ({ onSignInClick }) => { +const standards = [ + { + title: "CIS Benchmarks", + description: "Center for Internet Security Microsoft 365 Foundations", + }, + { title: "NIST Framework", description: "National Institute of Standards and Technology guidelines" }, + { title: "ISO 27001", description: "International standard for information security management" }, + { title: "ASD Essential Eight", description: "Australian Signals Directorate mitigation strategies" }, +]; + +const AboutUs = ({ onSignInClick = () => {} }) => { return ( -
- +
+ -
-
- AutoAudit Logo -

About AutoAudit

-

- Revolutionizing Cloud Compliance for Modern Enterprises -

-
+
+
+ AutoAudit +

About AutoAudit

+

Revolutionizing Cloud Compliance for Modern Enterprises

+
-
-

Our Mission

-

- AutoAudit empowers organizations to maintain robust security postures in their Microsoft 365 environments - through automated compliance monitoring and assessment. We bridge the gap between complex regulatory - requirements and practical implementation, making enterprise-grade security accessible to teams of all sizes. -

+
+

Our Mission

+
+

+ AutoAudit empowers organizations to maintain robust security postures in their Microsoft 365 + environments through automated compliance monitoring and assessment. We bridge the gap between complex + regulatory requirements and practical implementation, making enterprise-grade security accessible to + teams of all sizes. +

+
-
-

What We Do

-
- {landingFeatures.map((feature) => ( -
- -
-

{feature.title}

-

{feature.description}

-
+
+

What We Do

+
+ {features.map((feature) => ( +
+
{feature.icon}
+

{feature.title}

+

{feature.description}

))}
-
-

Why AutoAudit?

-

- In today's rapidly evolving threat landscape, manual compliance checks are no longer sufficient. - Cloud misconfigurations remain one of the leading causes of data breaches, with organizations - struggling to maintain visibility across their expanding cloud footprint. -

-

- AutoAudit was born from the recognition that compliance shouldn't be a burden. Our platform - transforms complex regulatory frameworks into automated, actionable insights, enabling your - security team to focus on strategic initiatives rather than manual auditing tasks. -

+
+

Why AutoAudit?

+
+

+ In today's rapidly evolving threat landscape, manual compliance reviews become inefficient. Cloud + misconfiguration remains one of the leading causes of data breaches, with organizations struggling to + maintain visibility across their expanding cloud footprints. +

+
+

+ AutoAudit was born from the recognition that compliance shouldn't be a burden. Our platform + transforms complex regulatory frameworks into automated, actionable insights, enabling your security team + to focus on strategic priorities rather than manual auditing tasks. +

+
-
-

Industry Standards We Support

+
+

Industry Standards We Support

-
-

CIS Benchmarks

-

Center for Internet Security Microsoft 365 Foundations

-
-
-

NIST Framework

-

National Institute of Standards and Technology guidelines

-
-
-

ISO 27001

-

International standard for information security management

-
-
-

ASD Essential Eight

-

Australian Signals Directorate mitigation strategies

-
+ {standards.map((standard) => ( +
+

{standard.title}

+

{standard.description}

+
+ ))}
-
-

Our Commitment

-

- We're committed to providing enterprise-grade security tools that are both powerful and accessible. - Our team continuously researches emerging threats and evolving compliance requirements to ensure - AutoAudit remains at the forefront of cloud security posture management. -

-

- Privacy and security are fundamental to everything we do. We employ bank-level encryption, - follow zero-trust principles, and maintain strict data governance practices to protect your - sensitive information. -

+
+

Our Commitment

+
+

+ We're committed to providing enterprise-grade security tools that are both powerful and accessible. + Our team continuously researches emerging threats and evolving compliance requirements to ensure AutoAudit + remains at the forefront of cloud security posture management. +

+
+

+ Privacy and security are fundamental to everything we do. We employ bank-level encryption, follow + zero-trust principles, and maintain strict data governance practices to protect your sensitive + information. +

+
-
+

Ready to Transform Your Compliance Strategy?

Join organizations worldwide who trust AutoAudit to secure their cloud environments.

-
-
+
diff --git a/frontend/src/pages/Landing/components/LandingHeader.js b/frontend/src/pages/Landing/components/LandingHeader.js index 9a18a532..e29d07fa 100644 --- a/frontend/src/pages/Landing/components/LandingHeader.js +++ b/frontend/src/pages/Landing/components/LandingHeader.js @@ -8,7 +8,7 @@ const navLinks = [ { label: "Contact", href: "/contact" }, ]; -const LandingHeader = ({ onSignInClick, showSignIn = true }) => { +const LandingHeader = ({ onSignInClick, hiddenLinks = [], showSignIn = true }) => { return (
@@ -16,11 +16,13 @@ const LandingHeader = ({ onSignInClick, showSignIn = true }) => {
+ {error && ( +
+ + {error} +
+ )} +
@@ -52,6 +87,7 @@ const SignInPanel = ({ onLogin, onSignUpClick }) => { value={formData.email} onChange={handleChange} required + disabled={isLoading} />
@@ -68,12 +104,14 @@ const SignInPanel = ({ onLogin, onSignUpClick }) => { value={formData.password} onChange={handleChange} required + disabled={isLoading} /> @@ -95,9 +133,18 @@ const SignInPanel = ({ onLogin, onSignUpClick }) => {
- diff --git a/frontend/src/pages/Connections/ConnectionsPage.css b/frontend/src/pages/Connections/ConnectionsPage.css new file mode 100644 index 00000000..fc035928 --- /dev/null +++ b/frontend/src/pages/Connections/ConnectionsPage.css @@ -0,0 +1,318 @@ +.connections-page { + min-height: 100vh; + background: var(--bg-primary); + padding: 24px; + transition: background-color 0.3s ease; +} + +.connections-page.light { + --bg-primary: #f8fafc; + --bg-secondary: #ffffff; + --bg-tertiary: #e2e8f0; + --text-primary: #1e293b; + --text-secondary: #64748b; + --text-tertiary: #94a3b8; + --border-color: #e2e8f0; +} + +.connections-container { + max-width: 1000px; + margin: 0 auto; +} + +.page-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 24px; +} + +.page-header .header-content { + display: flex; + align-items: center; + gap: 16px; + color: var(--text-primary); +} + +.page-header .header-content svg { + color: var(--teal, #64DFDF); +} + +.page-header .header-text h1 { + font-size: 24px; + font-weight: bold; + color: var(--text-primary); + margin: 0; +} + +.page-header .header-text p { + color: var(--text-secondary); + font-size: 14px; + margin: 0; +} + +.error-banner { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.3); + border-radius: 8px; + color: #ef4444; + margin-bottom: 24px; +} + +.connection-form-card { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; +} + +.connection-form-card h3 { + color: var(--text-primary); + font-size: 18px; + font-weight: 600; + margin: 0 0 20px 0; +} + +.form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; +} + +.form-group { + margin-bottom: 16px; +} + +.form-group label { + display: block; + color: var(--text-secondary); + font-size: 14px; + font-weight: 500; + margin-bottom: 8px; +} + +.form-group input, +.form-group select { + width: 100%; + padding: 10px 14px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + color: var(--text-primary); + font-size: 14px; + transition: border-color 0.2s ease; +} + +.form-group input:focus, +.form-group select:focus { + outline: none; + border-color: var(--teal, #64DFDF); +} + +.form-group input::placeholder { + color: var(--text-tertiary); +} + +.form-group input:disabled, +.form-group select:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.form-actions { + display: flex; + justify-content: flex-end; + gap: 12px; + margin-top: 8px; +} + +.connections-list { + display: flex; + flex-direction: column; + gap: 16px; +} + +.connection-card { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 20px; + display: flex; + align-items: center; + justify-content: space-between; + transition: border-color 0.2s ease; +} + +.connection-card:hover { + border-color: var(--teal, #64DFDF); +} + +.connection-info { + flex: 1; +} + +.connection-header { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 8px; +} + +.connection-header h4 { + color: var(--text-primary); + font-size: 16px; + font-weight: 600; + margin: 0; +} + +.status-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 10px; + border-radius: 20px; + font-size: 12px; + font-weight: 500; +} + +.status-badge.connected { + background: rgba(16, 185, 129, 0.15); + color: #10b981; +} + +.status-badge.failed { + background: rgba(239, 68, 68, 0.15); + color: #ef4444; +} + +.status-badge.pending { + background: rgba(249, 115, 22, 0.15); + color: #f97316; +} + +.status-icon.success { + color: #10b981; +} + +.status-icon.error { + color: #ef4444; +} + +.status-icon.pending { + color: #f97316; +} + +.connection-details { + display: flex; + flex-wrap: wrap; + gap: 16px; +} + +.detail-item { + color: var(--text-secondary); + font-size: 13px; +} + +.detail-item strong { + color: var(--text-tertiary); + font-weight: 500; +} + +.connection-actions { + display: flex; + gap: 8px; +} + +.toolbar-button.danger { + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.3); + color: #ef4444; +} + +.toolbar-button.danger:hover:not(:disabled) { + background: rgba(239, 68, 68, 0.2); + border-color: rgba(239, 68, 68, 0.5); +} + +.toolbar-button.danger:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.empty-state { + text-align: center; + padding: 60px 20px; + background: var(--bg-secondary); + border: 1px dashed var(--border-color); + border-radius: 12px; +} + +.empty-state svg { + color: var(--text-tertiary); + margin-bottom: 16px; +} + +.empty-state h3 { + color: var(--text-primary); + font-size: 18px; + font-weight: 600; + margin: 0 0 8px 0; +} + +.empty-state p { + color: var(--text-secondary); + font-size: 14px; + margin: 0; +} + +.loading-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 60px 20px; + color: var(--text-secondary); +} + +.loading-state p { + margin-top: 16px; +} + +.spinning { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (max-width: 768px) { + .form-row { + grid-template-columns: 1fr; + } + + .connection-card { + flex-direction: column; + align-items: flex-start; + gap: 16px; + } + + .connection-actions { + width: 100%; + } + + .connection-actions button { + flex: 1; + justify-content: center; + } +} diff --git a/frontend/src/pages/Connections/ConnectionsPage.js b/frontend/src/pages/Connections/ConnectionsPage.js new file mode 100644 index 00000000..4fd3faec --- /dev/null +++ b/frontend/src/pages/Connections/ConnectionsPage.js @@ -0,0 +1,459 @@ +import React, { useState, useEffect } from 'react'; +import { Plus, Link2, AlertCircle, Loader2, RefreshCw, Pencil, Trash2 } from 'lucide-react'; +import { useAuth } from '../../context/AuthContext'; +import { getPlatforms, getConnections, createConnection, updateConnection, deleteConnection } from '../../api/client'; +import './ConnectionsPage.css'; + +const ConnectionsPage = ({ isDarkMode }) => { + const { token } = useAuth(); + const [platforms, setPlatforms] = useState([]); + const [connections, setConnections] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [showForm, setShowForm] = useState(false); + const [formData, setFormData] = useState({ + name: '', + platform_id: '', + tenant_id: '', + client_id: '', + client_secret: '', + }); + const [isSubmitting, setIsSubmitting] = useState(false); + const [editingConnection, setEditingConnection] = useState(null); + const [editFormData, setEditFormData] = useState({ + name: '', + tenant_id: '', + client_id: '', + client_secret: '', + }); + const [isEditing, setIsEditing] = useState(false); + const [deletingId, setDeletingId] = useState(null); + + useEffect(() => { + loadData(); + }, [token]); + + async function loadData() { + setIsLoading(true); + setError(null); + try { + const [platformsData, connectionsData] = await Promise.all([ + getPlatforms(token), + getConnections(token), + ]); + setPlatforms(platformsData); + setConnections(connectionsData); + } catch (err) { + setError(err.message || 'Failed to load data'); + } finally { + setIsLoading(false); + } + } + + function handleChange(e) { + const { name, value } = e.target; + setFormData(prev => ({ ...prev, [name]: value })); + } + + async function handleSubmit(e) { + e.preventDefault(); + setIsSubmitting(true); + setError(null); + + try { + const newConnection = await createConnection(token, { + name: formData.name, + tenant_id: formData.tenant_id, + client_id: formData.client_id, + client_secret: formData.client_secret, + }); + setConnections(prev => [...prev, newConnection]); + setFormData({ + name: '', + platform_id: '', + tenant_id: '', + client_id: '', + client_secret: '', + }); + setShowForm(false); + } catch (err) { + setError(err.message || 'Failed to create connection'); + } finally { + setIsSubmitting(false); + } + } + + function handleTestConnection() { + alert('Connection testing needs to be implemented'); + } + + function startEditing(connection) { + setEditingConnection(connection); + setEditFormData({ + name: connection.name, + tenant_id: connection.tenant_id, + client_id: connection.client_id, + client_secret: '', + }); + } + + function handleEditChange(e) { + const { name, value } = e.target; + setEditFormData(prev => ({ ...prev, [name]: value })); + } + + async function handleEditSubmit(e) { + e.preventDefault(); + setIsEditing(true); + setError(null); + + try { + const updateData = { + name: editFormData.name, + tenant_id: editFormData.tenant_id, + client_id: editFormData.client_id, + }; + + // Only include client_secret if user entered a new one + if (editFormData.client_secret) { + updateData.client_secret = editFormData.client_secret; + } + + const updatedConnection = await updateConnection(token, editingConnection.id, updateData); + setConnections(prev => + prev.map(conn => (conn.id === editingConnection.id ? updatedConnection : conn)) + ); + setEditingConnection(null); + } catch (err) { + setError(err.message || 'Failed to update connection'); + } finally { + setIsEditing(false); + } + } + + function cancelEditing() { + setEditingConnection(null); + setEditFormData({ + name: '', + tenant_id: '', + client_id: '', + client_secret: '', + }); + } + + async function handleDelete(id) { + if (!window.confirm('Are you sure you want to delete this connection? This action cannot be undone.')) { + return; + } + + setDeletingId(id); + setError(null); + + try { + await deleteConnection(token, id); + setConnections(prev => prev.filter(conn => conn.id !== id)); + } catch (err) { + setError(err.message || 'Failed to delete connection'); + } finally { + setDeletingId(null); + } + } + + if (isLoading) { + return ( +
+
+
+ +

Loading connections...

+
+
+
+ ); + } + + return ( +
+
+
+
+ +
+

Cloud Platforms

+

Manage your cloud platform connections

+
+
+ +
+ + {error && ( +
+ + {error} +
+ )} + + {showForm && ( +
+

New Connection

+
+
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ )} + + {editingConnection && ( +
+

Edit Connection

+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ )} + +
+ {connections.length === 0 ? ( +
+ +

No connections yet

+

Add your first M365 connection to start scanning.

+
+ ) : ( + connections.map(connection => ( +
+
+
+

{connection.name}

+
+
+ + Tenant ID: {connection.tenant_id} + + + Client ID: {connection.client_id} + +
+
+
+ + + +
+
+ )) + )} +
+
+
+ ); +}; + +export default ConnectionsPage; diff --git a/frontend/src/pages/Scans/ScanDetailPage.css b/frontend/src/pages/Scans/ScanDetailPage.css new file mode 100644 index 00000000..4c867d30 --- /dev/null +++ b/frontend/src/pages/Scans/ScanDetailPage.css @@ -0,0 +1,535 @@ +.scan-detail-page { + min-height: 100vh; + background: var(--bg-primary); + padding: 24px; + transition: background-color 0.3s ease; +} + +.scan-detail-page.light { + --bg-primary: #f8fafc; + --bg-secondary: #ffffff; + --bg-tertiary: #e2e8f0; + --text-primary: #1e293b; + --text-secondary: #64748b; + --text-tertiary: #94a3b8; + --border-color: #e2e8f0; +} + +.scan-detail-container { + max-width: 1000px; + margin: 0 auto; +} + +.page-header { + margin-bottom: 24px; +} + +.back-button { + display: inline-flex; + align-items: center; + gap: 8px; + color: var(--text-secondary); + background: none; + border: none; + font-size: 14px; + cursor: pointer; + padding: 8px 0; + transition: color 0.2s ease; +} + +.back-button:hover { + color: var(--text-primary); +} + +.scan-header-card { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; +} + +.scan-header-content { + display: flex; + align-items: center; + gap: 16px; + margin-bottom: 20px; +} + +.scan-icon { + width: 56px; + height: 56px; + border-radius: 12px; + background: rgba(100, 223, 223, 0.15); + display: flex; + align-items: center; + justify-content: center; + color: var(--teal, #64DFDF); +} + +.scan-info { + flex: 1; +} + +.scan-info h1 { + color: var(--text-primary); + font-size: 22px; + font-weight: 600; + margin: 0 0 4px 0; +} + +.scan-info p { + color: var(--text-secondary); + font-size: 14px; + margin: 0; +} + +.status-badge.large { + padding: 8px 16px; + font-size: 14px; +} + +.scan-meta { + display: flex; + gap: 32px; + padding-top: 16px; + border-top: 1px solid var(--border-color); +} + +.meta-item { + display: flex; + flex-direction: column; + gap: 4px; +} + +.meta-label { + color: var(--text-tertiary); + font-size: 12px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.meta-value { + color: var(--text-primary); + font-size: 14px; +} + +.progress-card { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 24px; + text-align: center; + margin-bottom: 24px; +} + +.progress-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; +} + +.progress-content svg { + color: var(--teal, #64DFDF); +} + +.progress-text h3 { + color: var(--text-primary); + font-size: 18px; + font-weight: 600; + margin: 0 0 4px 0; +} + +.progress-text p { + color: var(--text-secondary); + font-size: 14px; + margin: 0; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 16px; + margin-bottom: 24px; +} + +.stat-card { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 20px; + display: flex; + align-items: center; + gap: 16px; +} + +.stat-card .stat-icon { + width: 44px; + height: 44px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; +} + +.stat-card.total .stat-icon { + background: rgba(100, 116, 139, 0.15); + color: var(--text-secondary); +} + +.stat-card.passed .stat-icon { + background: rgba(16, 185, 129, 0.15); + color: #10b981; +} + +.stat-card.failed .stat-icon { + background: rgba(239, 68, 68, 0.15); + color: #ef4444; +} + +.stat-card.errors .stat-icon { + background: rgba(249, 115, 22, 0.15); + color: #f97316; +} + +.stat-card .stat-content { + display: flex; + flex-direction: column; +} + +.stat-card .stat-value { + font-size: 28px; + font-weight: 700; + color: var(--text-primary); + line-height: 1; +} + +.stat-card.passed .stat-value { + color: #10b981; +} + +.stat-card.failed .stat-value { + color: #ef4444; +} + +.stat-card.errors .stat-value { + color: #f97316; +} + +.stat-card .stat-label { + font-size: 13px; + color: var(--text-secondary); + margin-top: 4px; +} + +.results-section { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 24px; +} + +.results-section h2 { + color: var(--text-primary); + font-size: 18px; + font-weight: 600; + margin: 0 0 20px 0; +} + +.results-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.result-card { + background: var(--bg-tertiary); + border-radius: 8px; + padding: 16px; + border-left: 3px solid transparent; +} + +.result-card.pass, +.result-card.passed { + border-left-color: #10b981; +} + +.result-card.fail, +.result-card.failed { + border-left-color: #ef4444; +} + +.result-card.error { + border-left-color: #f97316; +} + +.result-card.pending { + border-left-color: #f97316; + opacity: 0.7; +} + +.result-card.skipped { + border-left-color: #94a3b8; + opacity: 0.6; +} + +.result-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; +} + +.result-title { + display: flex; + align-items: center; + gap: 10px; + flex: 1; +} + +.result-icon.pass, +.result-icon.passed { + color: #10b981; +} + +.result-icon.fail, +.result-icon.failed { + color: #ef4444; +} + +.result-icon.error { + color: #f97316; +} + +.result-icon.pending { + color: #f97316; +} + +.result-icon.skipped, +.result-icon.unknown { + color: var(--text-tertiary); +} + +.control-id { + font-family: monospace; + font-size: 12px; + color: var(--text-tertiary); + background: var(--bg-secondary); + padding: 2px 8px; + border-radius: 4px; +} + +.result-title h4 { + color: var(--text-primary); + font-size: 14px; + font-weight: 500; + margin: 0; +} + +.result-badge { + font-size: 11px; + font-weight: 600; + padding: 4px 10px; + border-radius: 20px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.result-badge.pass, +.result-badge.passed { + background: rgba(16, 185, 129, 0.15); + color: #10b981; +} + +.result-badge.fail, +.result-badge.failed { + background: rgba(239, 68, 68, 0.15); + color: #ef4444; +} + +.result-badge.error { + background: rgba(249, 115, 22, 0.15); + color: #f97316; +} + +.result-badge.pending { + background: rgba(249, 115, 22, 0.15); + color: #f97316; +} + +.result-badge.skipped { + background: rgba(100, 116, 139, 0.15); + color: var(--text-tertiary); +} + +.result-badge.unknown { + background: rgba(100, 116, 139, 0.15); + color: var(--text-secondary); +} + +.result-description { + color: var(--text-secondary); + font-size: 13px; + margin: 12px 0 0 26px; + line-height: 1.5; +} + +.result-message { + color: var(--text-tertiary); + font-size: 12px; + font-style: italic; + margin: 8px 0 0 26px; +} + +.error-card { + display: flex; + align-items: flex-start; + gap: 12px; + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.3); + border-radius: 12px; + padding: 20px; + margin-top: 24px; +} + +.error-card svg { + color: #ef4444; + flex-shrink: 0; +} + +.error-content h4 { + color: #ef4444; + font-size: 16px; + font-weight: 600; + margin: 0 0 8px 0; +} + +.error-content p { + color: var(--text-primary); + font-size: 14px; + margin: 0; +} + +.loading-state, +.error-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 80px 20px; + color: var(--text-secondary); + text-align: center; +} + +.loading-state p, +.error-state p { + margin: 16px 0 0 0; +} + +.error-state svg { + color: var(--text-tertiary); + margin-bottom: 8px; +} + +.error-state h3 { + color: var(--text-primary); + font-size: 20px; + font-weight: 600; + margin: 0 0 8px 0; +} + +.error-state button { + margin-top: 24px; +} + +.status-badge { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + border-radius: 20px; + font-size: 12px; + font-weight: 500; +} + +.status-badge.completed { + background: rgba(16, 185, 129, 0.15); + color: #10b981; +} + +.status-badge.failed { + background: rgba(239, 68, 68, 0.15); + color: #ef4444; +} + +.status-badge.running { + background: rgba(59, 130, 246, 0.15); + color: #3b82f6; +} + +.status-badge.pending { + background: rgba(249, 115, 22, 0.15); + color: #f97316; +} + +.status-icon.success { + color: #10b981; +} + +.status-icon.error { + color: #ef4444; +} + +.status-icon.running { + color: #3b82f6; +} + +.status-icon.pending { + color: #f97316; +} + +.spinning { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (max-width: 768px) { + .stats-grid { + grid-template-columns: repeat(2, 1fr); + } + + .scan-header-content { + flex-direction: column; + align-items: flex-start; + } + + .scan-meta { + flex-direction: column; + gap: 16px; + } + + .result-header { + flex-direction: column; + gap: 8px; + } + + .result-badge { + align-self: flex-start; + } +} + +@media (max-width: 480px) { + .stats-grid { + grid-template-columns: 1fr; + } +} diff --git a/frontend/src/pages/Scans/ScanDetailPage.js b/frontend/src/pages/Scans/ScanDetailPage.js new file mode 100644 index 00000000..4317dc31 --- /dev/null +++ b/frontend/src/pages/Scans/ScanDetailPage.js @@ -0,0 +1,307 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { + ArrowLeft, + CheckCircle, + XCircle, + Clock, + Loader2, + AlertCircle, + FileText, + Shield, + AlertTriangle, +} from 'lucide-react'; +import { useAuth } from '../../context/AuthContext'; +import { getScan } from '../../api/client'; +import './ScanDetailPage.css'; + +const ScanDetailPage = ({ isDarkMode }) => { + const { scanId } = useParams(); + const navigate = useNavigate(); + const { token } = useAuth(); + const [scan, setScan] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + const loadScan = useCallback(async () => { + try { + const scanData = await getScan(token, scanId); + setScan(scanData); + return scanData; + } catch (err) { + setError(err.message || 'Failed to load scan'); + return null; + } + }, [token, scanId]); + + useEffect(() => { + async function initialLoad() { + setIsLoading(true); + await loadScan(); + setIsLoading(false); + } + initialLoad(); + }, [loadScan]); + + // Poll for updates every 3 seconds while pending/running + useEffect(() => { + if (!scan || scan.status === 'completed' || scan.status === 'failed') { + return; + } + + const interval = setInterval(async () => { + const updatedScan = await loadScan(); + if (updatedScan?.status === 'completed' || updatedScan?.status === 'failed') { + clearInterval(interval); + } + }, 3000); + + return () => clearInterval(interval); + }, [scan, loadScan]); + + function getStatusIcon(status) { + switch (status) { + case 'completed': + return ; + case 'failed': + return ; + case 'running': + return ; + default: + return ; + } + } + + function getStatusText(status) { + switch (status) { + case 'completed': + return 'Completed'; + case 'failed': + return 'Failed'; + case 'running': + return 'Running'; + default: + return 'Pending'; + } + } + + function formatDate(dateString) { + if (!dateString) return '-'; + return new Date(dateString).toLocaleString(); + } + + function getResultIcon(status) { + switch (status) { + case 'passed': + return ; + case 'failed': + return ; + case 'error': + return ; + case 'pending': + return ; + default: + return ; + } + } + + function getResultBadgeText(status) { + switch (status) { + case 'passed': + return 'Pass'; + case 'failed': + return 'Fail'; + case 'error': + return 'Error'; + case 'pending': + return 'Pending'; + case 'skipped': + return 'Skipped'; + default: + return 'Unknown'; + } + } + + if (isLoading) { + return ( +
+
+
+ +

Loading scan details...

+
+
+
+ ); + } + + if (error || !scan) { + return ( +
+
+
+ +

Failed to load scan

+

{error || 'Scan not found'}

+ +
+
+
+ ); + } + + // Build summary from scan counts (API returns these directly) + const summary = { + total: scan.total_controls || 0, + passed: scan.passed_count || 0, + failed: scan.failed_count || 0, + errors: scan.error_count || 0, + pending: (scan.total_controls || 0) - (scan.passed_count || 0) - (scan.failed_count || 0) - (scan.error_count || 0) - (scan.skipped_count || 0), + }; + const results = scan.results || []; + + return ( +
+
+
+ +
+ +
+
+
+ +
+
+

{scan.benchmark || 'Compliance Scan'}

+

{scan.version || ''}

+
+ + {getStatusIcon(scan.status)} + {getStatusText(scan.status)} + +
+ +
+
+ Connection + {scan.connection_name || '-'} +
+
+ Started + {formatDate(scan.created_at)} +
+
+ Completed + {formatDate(scan.completed_at)} +
+
+
+ + {(scan.status === 'pending' || scan.status === 'running') && ( +
+
+ +
+

Scan in Progress

+

+ {scan.status === 'pending' + ? 'Waiting to start...' + : `Evaluating controls... ${summary.passed + summary.failed + summary.errors} of ${summary.total} complete`} +

+
+
+
+ )} + +
+
+
+ +
+
+ {summary.total} + Total Controls +
+
+
+
+ +
+
+ {summary.passed} + Passed +
+
+
+
+ +
+
+ {summary.failed} + Failed +
+
+
+
+ +
+
+ {summary.errors} + Errors +
+
+
+ + {results.length > 0 && ( +
+

Control Results

+
+ {results.map((result, index) => ( +
+
+
+ {getResultIcon(result.status)} + {result.control_id} +

{result.title || result.control_id}

+
+ + {getResultBadgeText(result.status)} + +
+ {result.description && ( +

{result.description}

+ )} + {result.message && ( +

{result.message}

+ )} +
+ ))} +
+
+ )} + + {scan.status === 'failed' && scan.error && ( +
+ +
+

Scan Failed

+

{scan.error}

+
+
+ )} +
+
+ ); +}; + +export default ScanDetailPage; diff --git a/frontend/src/pages/Scans/ScansPage.css b/frontend/src/pages/Scans/ScansPage.css new file mode 100644 index 00000000..7ff8be9a --- /dev/null +++ b/frontend/src/pages/Scans/ScansPage.css @@ -0,0 +1,317 @@ +.scans-page { + min-height: 100vh; + background: var(--bg-primary); + padding: 24px; + transition: background-color 0.3s ease; +} + +.scans-page.light { + --bg-primary: #f8fafc; + --bg-secondary: #ffffff; + --bg-tertiary: #e2e8f0; + --text-primary: #1e293b; + --text-secondary: #64748b; + --text-tertiary: #94a3b8; + --border-color: #e2e8f0; +} + +.scans-container { + max-width: 1200px; + margin: 0 auto; +} + +.page-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 24px; +} + +.page-header .header-content { + display: flex; + align-items: center; + gap: 16px; + color: var(--text-primary); +} + +.page-header .header-content svg { + color: var(--teal, #64DFDF); +} + +.page-header .header-text h1 { + font-size: 24px; + font-weight: bold; + color: var(--text-primary); + margin: 0; +} + +.page-header .header-text p { + color: var(--text-secondary); + font-size: 14px; + margin: 0; +} + +.error-banner, +.warning-banner { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + border-radius: 8px; + margin-bottom: 24px; +} + +.error-banner { + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.3); + color: #ef4444; +} + +.warning-banner { + background: rgba(249, 115, 22, 0.1); + border: 1px solid rgba(249, 115, 22, 0.3); + color: #f97316; +} + +.scan-form-card { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; +} + +.scan-form-card h3 { + color: var(--text-primary); + font-size: 18px; + font-weight: 600; + margin: 0 0 20px 0; +} + +.form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; +} + +.form-group { + margin-bottom: 16px; +} + +.form-group label { + display: block; + color: var(--text-secondary); + font-size: 14px; + font-weight: 500; + margin-bottom: 8px; +} + +.form-group select { + width: 100%; + padding: 10px 14px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + color: var(--text-primary); + font-size: 14px; + transition: border-color 0.2s ease; + cursor: pointer; +} + +.form-group select:focus { + outline: none; + border-color: var(--teal, #64DFDF); +} + +.form-group select:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.form-actions { + display: flex; + justify-content: flex-end; + gap: 12px; + margin-top: 8px; +} + +.scans-list { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 12px; + overflow: hidden; +} + +.scans-table { + width: 100%; + border-collapse: collapse; +} + +.scans-table th { + text-align: left; + padding: 16px 20px; + color: var(--text-secondary); + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + border-bottom: 1px solid var(--border-color); + background: var(--bg-tertiary); +} + +.scans-table td { + padding: 16px 20px; + color: var(--text-primary); + font-size: 14px; + border-bottom: 1px solid var(--border-color); +} + +.scan-row { + cursor: pointer; + transition: background-color 0.2s ease; +} + +.scan-row:hover { + background: var(--bg-tertiary); +} + +.scan-row:last-child td { + border-bottom: none; +} + +.status-badge { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + border-radius: 20px; + font-size: 12px; + font-weight: 500; +} + +.status-badge.completed { + background: rgba(16, 185, 129, 0.15); + color: #10b981; +} + +.status-badge.failed { + background: rgba(239, 68, 68, 0.15); + color: #ef4444; +} + +.status-badge.running { + background: rgba(59, 130, 246, 0.15); + color: #3b82f6; +} + +.status-badge.pending { + background: rgba(249, 115, 22, 0.15); + color: #f97316; +} + +.status-icon.success { + color: #10b981; +} + +.status-icon.error { + color: #ef4444; +} + +.status-icon.running { + color: #3b82f6; +} + +.status-icon.pending { + color: #f97316; +} + +.benchmark-name { + display: block; + font-weight: 500; +} + +.benchmark-version { + display: block; + font-size: 12px; + color: var(--text-tertiary); +} + +.results-summary { + display: flex; + gap: 12px; + font-size: 13px; +} + +.results-summary .passed { + color: #10b981; +} + +.results-summary .failed { + color: #ef4444; +} + +.empty-state { + text-align: center; + padding: 60px 20px; +} + +.empty-state svg { + color: var(--text-tertiary); + margin-bottom: 16px; +} + +.empty-state h3 { + color: var(--text-primary); + font-size: 18px; + font-weight: 600; + margin: 0 0 8px 0; +} + +.empty-state p { + color: var(--text-secondary); + font-size: 14px; + margin: 0; +} + +.loading-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 60px 20px; + color: var(--text-secondary); +} + +.loading-state p { + margin-top: 16px; +} + +.spinning { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (max-width: 768px) { + .form-row { + grid-template-columns: 1fr; + } + + .scans-table { + display: block; + overflow-x: auto; + } + + .page-header { + flex-direction: column; + align-items: flex-start; + gap: 16px; + } +} diff --git a/frontend/src/pages/Scans/ScansPage.js b/frontend/src/pages/Scans/ScansPage.js new file mode 100644 index 00000000..5991e6d5 --- /dev/null +++ b/frontend/src/pages/Scans/ScansPage.js @@ -0,0 +1,320 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Search, Plus, CheckCircle, XCircle, Clock, Loader2, AlertCircle, PlayCircle } from 'lucide-react'; +import { useAuth } from '../../context/AuthContext'; +import { getScans, getConnections, getBenchmarks, createScan } from '../../api/client'; +import './ScansPage.css'; + +const ScansPage = ({ isDarkMode }) => { + const navigate = useNavigate(); + const { token } = useAuth(); + const [scans, setScans] = useState([]); + const [connections, setConnections] = useState([]); + const [benchmarks, setBenchmarks] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [showForm, setShowForm] = useState(false); + const [formData, setFormData] = useState({ + m365_connection_id: '', + benchmark_key: '', + }); + const [isSubmitting, setIsSubmitting] = useState(false); + + const loadScans = useCallback(async () => { + try { + const scansData = await getScans(token); + setScans(scansData); + } catch (err) { + console.error('Failed to refresh scans:', err); + } + }, [token]); + + useEffect(() => { + async function loadData() { + setIsLoading(true); + setError(null); + try { + const [scansData, connectionsData, benchmarksData] = await Promise.all([ + getScans(token), + getConnections(token), + getBenchmarks(token), + ]); + setScans(scansData); + setConnections(connectionsData); + setBenchmarks(benchmarksData); + } catch (err) { + setError(err.message || 'Failed to load data'); + } finally { + setIsLoading(false); + } + } + + loadData(); + }, [token]); + + // Poll for scan updates every 5 seconds + useEffect(() => { + const hasPendingScans = scans.some( + scan => scan.status === 'pending' || scan.status === 'running' + ); + + if (!hasPendingScans) return; + + const interval = setInterval(loadScans, 5000); + return () => clearInterval(interval); + }, [scans, loadScans]); + + function handleChange(e) { + const { name, value } = e.target; + setFormData(prev => ({ ...prev, [name]: value })); + } + + async function handleSubmit(e) { + e.preventDefault(); + setIsSubmitting(true); + setError(null); + + try { + // Parse the benchmark key (format: "framework|benchmark|version") + const [framework, benchmark, version] = formData.benchmark_key.split('|'); + + const newScan = await createScan(token, { + m365_connection_id: parseInt(formData.m365_connection_id), + framework, + benchmark, + version, + }); + + setScans(prev => [newScan, ...prev]); + setFormData({ m365_connection_id: '', benchmark_key: '' }); + setShowForm(false); + } catch (err) { + setError(err.message || 'Failed to create scan'); + } finally { + setIsSubmitting(false); + } + } + + function getStatusIcon(status) { + switch (status) { + case 'completed': + return ; + case 'failed': + return ; + case 'running': + return ; + default: + return ; + } + } + + function getStatusText(status) { + switch (status) { + case 'completed': + return 'Completed'; + case 'failed': + return 'Failed'; + case 'running': + return 'Running'; + default: + return 'Pending'; + } + } + + function formatDate(dateString) { + if (!dateString) return '-'; + return new Date(dateString).toLocaleString(); + } + + if (isLoading) { + return ( +
+
+
+ +

Loading scans...

+
+
+
+ ); + } + + return ( +
+
+
+
+ +
+

Compliance Scans

+

Run and manage compliance scans against your M365 connections

+
+
+ +
+ + {error && ( +
+ + {error} +
+ )} + + {connections.length === 0 && !isLoading && ( +
+ + You need to add a connection before you can run scans. +
+ )} + + {showForm && ( +
+

New Compliance Scan

+
+
+
+ + +
+ +
+ + +
+
+ +
+ + +
+
+
+ )} + +
+ {scans.length === 0 ? ( +
+ +

No scans yet

+

Run your first compliance scan to see results here.

+
+ ) : ( + + + + + + + + + + + + {scans.map(scan => ( + navigate(`/scans/${scan.id}`)} + className="scan-row" + > + + + + + + + ))} + +
StatusBenchmarkConnectionStartedResults
+ + {getStatusIcon(scan.status)} + {getStatusText(scan.status)} + + + + {scan.benchmark || '-'} + + {scan.version || ''} + {scan.connection_name || '-'}{formatDate(scan.created_at)} + {scan.status === 'completed' || scan.status === 'running' ? ( +
+ {scan.passed_count || 0} passed + {scan.failed_count || 0} failed + {scan.status === 'running' && scan.total_controls > 0 && ( + + ({scan.passed_count + scan.failed_count + scan.error_count || 0}/{scan.total_controls}) + + )} +
+ ) : ( + '-' + )} +
+ )} +
+
+
+ ); +}; + +export default ScansPage; From 1328c8fe0e0e46837fae2e891353dd928a8dd6e0 Mon Sep 17 00:00:00 2001 From: David Hartley Date: Thu, 11 Dec 2025 21:43:09 +1100 Subject: [PATCH 026/111] Add models and endpoints for connection config and scanning --- backend-api/alembic/env.py | 8 +- ...5f6g789_add_m365_connection_update_scan.py | 165 ++++++++++++ ...f6g7h890_database_architecture_revision.py | 238 ++++++++++++++++++ backend-api/app/api/v1/benchmarks.py | 149 +++++++++++ backend-api/app/api/v1/evidence.py | 4 +- backend-api/app/api/v1/m365_connections.py | 170 +++++++++++++ backend-api/app/api/v1/platforms.py | 41 +++ backend-api/app/api/v1/router.py | 18 +- backend-api/app/api/v1/scans.py | 185 ++++++++++++++ backend-api/app/core/config.py | 13 + backend-api/app/main.py | 8 +- backend-api/app/models/__init__.py | 18 +- backend-api/app/models/aws_connection.py | 13 + backend-api/app/models/azure_connection.py | 13 + backend-api/app/models/compliance.py | 105 ++++---- backend-api/app/models/gcp_connection.py | 13 + backend-api/app/models/m365_connection.py | 51 ++++ backend-api/app/models/platform.py | 17 ++ backend-api/app/models/scan_result.py | 40 +++ backend-api/app/models/user.py | 15 +- backend-api/app/schemas/__init__.py | 6 - backend-api/app/schemas/benchmark.py | 29 +++ backend-api/app/schemas/m365_connection.py | 50 ++++ backend-api/app/schemas/platform.py | 14 ++ backend-api/app/schemas/scan.py | 101 ++++++++ backend-api/app/services/benchmark_reader.py | 123 +++++++++ backend-api/app/services/celery_client.py | 57 +++++ backend-api/app/services/encryption.py | 58 +++++ backend-api/pyproject.toml | 4 + backend-api/uv.lock | 192 ++++++++++++++ 30 files changed, 1840 insertions(+), 78 deletions(-) create mode 100644 backend-api/alembic/versions/c3d4e5f6g789_add_m365_connection_update_scan.py create mode 100644 backend-api/alembic/versions/d4e5f6g7h890_database_architecture_revision.py create mode 100644 backend-api/app/api/v1/benchmarks.py create mode 100644 backend-api/app/api/v1/m365_connections.py create mode 100644 backend-api/app/api/v1/platforms.py create mode 100644 backend-api/app/api/v1/scans.py create mode 100644 backend-api/app/models/aws_connection.py create mode 100644 backend-api/app/models/azure_connection.py create mode 100644 backend-api/app/models/gcp_connection.py create mode 100644 backend-api/app/models/m365_connection.py create mode 100644 backend-api/app/models/platform.py create mode 100644 backend-api/app/models/scan_result.py create mode 100644 backend-api/app/schemas/benchmark.py create mode 100644 backend-api/app/schemas/m365_connection.py create mode 100644 backend-api/app/schemas/platform.py create mode 100644 backend-api/app/schemas/scan.py create mode 100644 backend-api/app/services/benchmark_reader.py create mode 100644 backend-api/app/services/celery_client.py create mode 100644 backend-api/app/services/encryption.py diff --git a/backend-api/alembic/env.py b/backend-api/alembic/env.py index 3e1ec40e..ccdd56a1 100644 --- a/backend-api/alembic/env.py +++ b/backend-api/alembic/env.py @@ -10,7 +10,13 @@ # Import models for autogenerate support from app.db.base import Base from app.models.user import User # noqa -from app.models.compliance import Tenant, Rule, Scan, Issue # noqa +from app.models.m365_connection import M365Connection # noqa +from app.models.platform import Platform # noqa +from app.models.compliance import Scan # noqa +from app.models.scan_result import ScanResult # noqa +from app.models.azure_connection import AzureConnection # noqa +from app.models.gcp_connection import GCPConnection # noqa +from app.models.aws_connection import AWSConnection # noqa from app.core.config import get_settings # this is the Alembic Config object, which provides diff --git a/backend-api/alembic/versions/c3d4e5f6g789_add_m365_connection_update_scan.py b/backend-api/alembic/versions/c3d4e5f6g789_add_m365_connection_update_scan.py new file mode 100644 index 00000000..3baf578e --- /dev/null +++ b/backend-api/alembic/versions/c3d4e5f6g789_add_m365_connection_update_scan.py @@ -0,0 +1,165 @@ +"""Add m365_connection table and update scan table + +Revision ID: c3d4e5f6g789 +Revises: b1c2d3e4f567 +Create Date: 2024-12-09 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + + +# revision identifiers, used by Alembic. +revision: str = "c3d4e5f6g789" +down_revision: Union[str, Sequence[str], None] = "b1c2d3e4f567" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Add m365_connection table, update scan table, drop tenant table.""" + + # Create m365_connection table + op.create_table( + "m365_connection", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("user_id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(length=255), nullable=False), + sa.Column("tenant_id", sa.String(length=255), nullable=False), + sa.Column("client_id", sa.String(length=255), nullable=False), + sa.Column("encrypted_client_secret", sa.Text(), nullable=False), + sa.Column("is_active", sa.Boolean(), nullable=False, server_default="true"), + sa.Column( + "created_at", sa.DateTime(), server_default=sa.text("now()"), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(), server_default=sa.text("now()"), nullable=False + ), + sa.ForeignKeyConstraint( + ["user_id"], + ["user.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_m365_connection_user_id"), + "m365_connection", + ["user_id"], + unique=False, + ) + + # Update scan table: add new columns + op.add_column( + "scan", + sa.Column("m365_connection_id", sa.Integer(), nullable=True), + ) + op.add_column( + "scan", + sa.Column("framework", sa.String(length=50), nullable=True), + ) + op.add_column( + "scan", + sa.Column("benchmark", sa.String(length=100), nullable=True), + ) + op.add_column( + "scan", + sa.Column("version", sa.String(length=20), nullable=True), + ) + op.add_column( + "scan", + sa.Column( + "control_ids", postgresql.JSONB(astext_type=sa.Text()), nullable=True + ), + ) + + # Add control_id to issue table + op.add_column( + "issue", + sa.Column("control_id", sa.String(length=50), nullable=True), + ) + + # Create foreign key for m365_connection_id + op.create_foreign_key( + "fk_scan_m365_connection_id", + "scan", + "m365_connection", + ["m365_connection_id"], + ["id"], + ) + op.create_index( + op.f("ix_scan_m365_connection_id"), + "scan", + ["m365_connection_id"], + unique=False, + ) + + # Drop tenant_id FK and column from scan + op.drop_index(op.f("ix_scan_tenant_id"), table_name="scan") + op.drop_constraint("scan_tenant_id_fkey", "scan", type_="foreignkey") + op.drop_column("scan", "tenant_id") + + # Drop tenant table + op.drop_table("tenant") + + # Make framework/benchmark/version NOT NULL for new scans + # (existing rows need to be handled - setting defaults first) + op.execute("UPDATE scan SET framework = 'unknown' WHERE framework IS NULL") + op.execute("UPDATE scan SET benchmark = 'unknown' WHERE benchmark IS NULL") + op.execute("UPDATE scan SET version = 'unknown' WHERE version IS NULL") + + op.alter_column("scan", "framework", nullable=False) + op.alter_column("scan", "benchmark", nullable=False) + op.alter_column("scan", "version", nullable=False) + + +def downgrade() -> None: + """Reverse the migration.""" + + # Make framework/benchmark/version nullable again + op.alter_column("scan", "version", nullable=True) + op.alter_column("scan", "benchmark", nullable=True) + op.alter_column("scan", "framework", nullable=True) + + # Recreate tenant table + op.create_table( + "tenant", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(length=255), nullable=False), + sa.Column("external_tenant_id", sa.String(length=255), nullable=True), + sa.Column( + "created_at", sa.DateTime(), server_default=sa.text("now()"), nullable=False + ), + sa.PrimaryKeyConstraint("id"), + ) + + # Add tenant_id back to scan + op.add_column( + "scan", + sa.Column("tenant_id", sa.Integer(), nullable=True), + ) + op.create_foreign_key( + "scan_tenant_id_fkey", "scan", "tenant", ["tenant_id"], ["id"] + ) + op.create_index(op.f("ix_scan_tenant_id"), "scan", ["tenant_id"], unique=False) + + # Drop m365_connection references from scan + op.drop_index(op.f("ix_scan_m365_connection_id"), table_name="scan") + op.drop_constraint("fk_scan_m365_connection_id", "scan", type_="foreignkey") + + # Drop new columns from issue + op.drop_column("issue", "control_id") + + # Drop new columns from scan + op.drop_column("scan", "control_ids") + op.drop_column("scan", "version") + op.drop_column("scan", "benchmark") + op.drop_column("scan", "framework") + op.drop_column("scan", "m365_connection_id") + + # Drop m365_connection table + op.drop_index(op.f("ix_m365_connection_user_id"), table_name="m365_connection") + op.drop_table("m365_connection") diff --git a/backend-api/alembic/versions/d4e5f6g7h890_database_architecture_revision.py b/backend-api/alembic/versions/d4e5f6g7h890_database_architecture_revision.py new file mode 100644 index 00000000..5dcfaf40 --- /dev/null +++ b/backend-api/alembic/versions/d4e5f6g7h890_database_architecture_revision.py @@ -0,0 +1,238 @@ +"""Database architecture revision + +- Create platform lookup table with seed data +- Create stub connection tables (azure, gcp, aws) +- Create scan_result table (replaces issue) +- Update scan table: add user_id FK, connection FKs, drop control_ids, add skipped_count, rename not_tested_count +- Drop issue and rule tables + +Revision ID: d4e5f6g7h890 +Revises: c3d4e5f6g789 +Create Date: 2024-12-10 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + + +# revision identifiers, used by Alembic. +revision: str = "d4e5f6g7h890" +down_revision: Union[str, Sequence[str], None] = "c3d4e5f6g789" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Apply database architecture revision.""" + + # 1. Create platform lookup table + op.create_table( + "platform", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(length=50), nullable=False), + sa.Column("display_name", sa.String(length=100), nullable=False), + sa.Column("is_active", sa.Boolean(), nullable=False, server_default="false"), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name"), + ) + + # Seed platform data - only M365 is active + op.execute(""" + INSERT INTO platform (name, display_name, is_active) VALUES + ('m365', 'Microsoft 365', true), + ('azure', 'Microsoft Azure', false), + ('gcp', 'Google Cloud Platform', false), + ('aws', 'Amazon Web Services', false) + """) + + # 2. Create stub connection tables (id only - columns added when implemented) + op.create_table( + "azure_connection", + sa.Column("id", sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + + op.create_table( + "gcp_connection", + sa.Column("id", sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + + op.create_table( + "aws_connection", + sa.Column("id", sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + + # 3. Create scan_result table (replaces issue) + op.create_table( + "scan_result", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("scan_id", sa.Integer(), nullable=False), + sa.Column("control_id", sa.String(length=50), nullable=False), + sa.Column("status", sa.String(length=20), nullable=False), + sa.Column("message", sa.Text(), nullable=True), + sa.Column("evidence", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column( + "created_at", sa.DateTime(), server_default=sa.text("now()"), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(), server_default=sa.text("now()"), nullable=False + ), + sa.ForeignKeyConstraint(["scan_id"], ["scan.id"]), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("scan_id", "control_id", name="uq_scan_result_scan_control"), + ) + op.create_index( + op.f("ix_scan_result_scan_id"), "scan_result", ["scan_id"], unique=False + ) + + # 4. Update scan table + # Add user_id FK (NOT NULL - starting fresh, but we need to handle existing rows) + op.add_column("scan", sa.Column("user_id", sa.Integer(), nullable=True)) + + # For any existing scans, assign them to user 1 (admin) if exists + # In practice, we're starting fresh so this shouldn't matter + op.execute(""" + UPDATE scan SET user_id = (SELECT id FROM "user" LIMIT 1) + WHERE user_id IS NULL + """) + + # Now make user_id NOT NULL + op.alter_column("scan", "user_id", nullable=False) + + op.create_foreign_key( + "fk_scan_user_id", "scan", "user", ["user_id"], ["id"] + ) + op.create_index(op.f("ix_scan_user_id"), "scan", ["user_id"], unique=False) + + # Add connection FKs with actual foreign key constraints + op.add_column( + "scan", sa.Column("azure_connection_id", sa.Integer(), nullable=True) + ) + op.create_foreign_key( + "fk_scan_azure_connection_id", + "scan", + "azure_connection", + ["azure_connection_id"], + ["id"], + ) + + op.add_column( + "scan", sa.Column("gcp_connection_id", sa.Integer(), nullable=True) + ) + op.create_foreign_key( + "fk_scan_gcp_connection_id", + "scan", + "gcp_connection", + ["gcp_connection_id"], + ["id"], + ) + + op.add_column( + "scan", sa.Column("aws_connection_id", sa.Integer(), nullable=True) + ) + op.create_foreign_key( + "fk_scan_aws_connection_id", + "scan", + "aws_connection", + ["aws_connection_id"], + ["id"], + ) + + # Remove control_ids - ScanResult records track selection + op.drop_column("scan", "control_ids") + + # Rename not_tested_count to error_count + op.alter_column("scan", "not_tested_count", new_column_name="error_count") + + # Add skipped_count + op.add_column( + "scan", + sa.Column("skipped_count", sa.Integer(), nullable=False, server_default="0"), + ) + + # 5. Drop issue table (replaced by scan_result) + op.drop_index(op.f("ix_issue_scan_id"), table_name="issue") + op.drop_table("issue") + + # 6. Drop rule table (unused) + op.drop_table("rule") + + +def downgrade() -> None: + """Reverse the database architecture revision.""" + + # 1. Recreate rule table + op.create_table( + "rule", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("framework", sa.String(length=100), nullable=False), + sa.Column("control_key", sa.String(length=100), nullable=False), + sa.Column("title", sa.Text(), nullable=False), + sa.Column("severity", sa.String(length=20), nullable=True), + sa.Column("description", sa.Text(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + + # 2. Recreate issue table + op.create_table( + "issue", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("scan_id", sa.Integer(), nullable=False), + sa.Column("rule_id", sa.Integer(), nullable=True), + sa.Column("control_id", sa.String(length=50), nullable=True), + sa.Column("priority", sa.String(length=20), nullable=True), + sa.Column("title", sa.Text(), nullable=True), + sa.Column("description", sa.Text(), nullable=True), + sa.Column("result", sa.String(length=20), nullable=True), + sa.Column("evidence", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column( + "created_at", sa.DateTime(), server_default=sa.text("now()"), nullable=False + ), + sa.ForeignKeyConstraint(["rule_id"], ["rule.id"]), + sa.ForeignKeyConstraint(["scan_id"], ["scan.id"]), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_issue_scan_id"), "issue", ["scan_id"], unique=False) + + # 3. Revert scan table changes + op.drop_column("scan", "skipped_count") + op.alter_column("scan", "error_count", new_column_name="not_tested_count") + op.add_column( + "scan", + sa.Column( + "control_ids", postgresql.JSONB(astext_type=sa.Text()), nullable=True + ), + ) + + # Drop connection FKs + op.drop_constraint("fk_scan_aws_connection_id", "scan", type_="foreignkey") + op.drop_column("scan", "aws_connection_id") + + op.drop_constraint("fk_scan_gcp_connection_id", "scan", type_="foreignkey") + op.drop_column("scan", "gcp_connection_id") + + op.drop_constraint("fk_scan_azure_connection_id", "scan", type_="foreignkey") + op.drop_column("scan", "azure_connection_id") + + # Drop user_id + op.drop_index(op.f("ix_scan_user_id"), table_name="scan") + op.drop_constraint("fk_scan_user_id", "scan", type_="foreignkey") + op.drop_column("scan", "user_id") + + # 4. Drop scan_result table + op.drop_index(op.f("ix_scan_result_scan_id"), table_name="scan_result") + op.drop_table("scan_result") + + # 5. Drop stub connection tables + op.drop_table("aws_connection") + op.drop_table("gcp_connection") + op.drop_table("azure_connection") + + # 6. Drop platform table + op.drop_table("platform") diff --git a/backend-api/app/api/v1/benchmarks.py b/backend-api/app/api/v1/benchmarks.py new file mode 100644 index 00000000..0276e291 --- /dev/null +++ b/backend-api/app/api/v1/benchmarks.py @@ -0,0 +1,149 @@ +"""Benchmark and control discovery API endpoints. + +These endpoints read from metadata.json files in the policies directory +to allow the frontend to discover available benchmarks and controls. +""" + +from fastapi import APIRouter, Depends, HTTPException, status + +from app.core.auth import get_current_user +from app.models.user import User +from app.schemas.benchmark import BenchmarkRead, ControlRead +from app.services.benchmark_reader import get_file_reader + +router = APIRouter(prefix="/benchmarks", tags=["Benchmarks"]) + + +@router.get("", response_model=list[BenchmarkRead]) +async def list_benchmarks( + current_user: User = Depends(get_current_user), +) -> list[BenchmarkRead]: + """List all available benchmarks. + + Scans the policies directory for metadata.json files and returns + information about each available benchmark. + """ + file_reader = get_file_reader() + benchmarks_data = file_reader.list_benchmarks() + + result = [] + for data in benchmarks_data: + controls = data.get("controls", []) + result.append( + BenchmarkRead( + framework=data.get("framework", ""), + slug=data.get("slug", ""), + version=data.get("version", ""), + name=data.get("benchmark", ""), + platform=data.get("platform", ""), + release_date=data.get("release_date"), + source_url=data.get("source_url"), + control_count=len(controls), + ) + ) + return result + + +@router.get("/{framework}/{slug}/{version}", response_model=BenchmarkRead) +async def get_benchmark( + framework: str, + slug: str, + version: str, + current_user: User = Depends(get_current_user), +) -> BenchmarkRead: + """Get details for a specific benchmark.""" + file_reader = get_file_reader() + + try: + data = file_reader.get_benchmark_metadata(framework, slug, version) + except FileNotFoundError: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Benchmark {framework}/{slug}/{version} not found", + ) + + controls = data.get("controls", []) + return BenchmarkRead( + framework=data.get("framework", framework), + slug=data.get("slug", slug), + version=data.get("version", version), + name=data.get("benchmark", ""), + platform=data.get("platform", ""), + release_date=data.get("release_date"), + source_url=data.get("source_url"), + control_count=len(controls), + ) + + +@router.get("/{framework}/{slug}/{version}/controls", response_model=list[ControlRead]) +async def list_controls( + framework: str, + slug: str, + version: str, + current_user: User = Depends(get_current_user), +) -> list[ControlRead]: + """List all controls for a specific benchmark.""" + file_reader = get_file_reader() + + try: + controls_data = file_reader.list_controls(framework, slug, version) + except FileNotFoundError: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Benchmark {framework}/{slug}/{version} not found", + ) + + result = [] + for control in controls_data: + result.append( + ControlRead( + control_id=control.get("control_id", ""), + title=control.get("title", ""), + description=control.get("description"), + severity=control.get("severity"), + service=control.get("service"), + data_collector_id=control.get("data_collector_id", ""), + policy_file=control.get("policy_file", ""), + requires_permissions=control.get("requires_permissions"), + ) + ) + return result + + +@router.get( + "/{framework}/{slug}/{version}/controls/{control_id}", + response_model=ControlRead, +) +async def get_control( + framework: str, + slug: str, + version: str, + control_id: str, + current_user: User = Depends(get_current_user), +) -> ControlRead: + """Get details for a specific control.""" + file_reader = get_file_reader() + + try: + control = file_reader.get_control_metadata(framework, slug, version, control_id) + except FileNotFoundError: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Benchmark {framework}/{slug}/{version} not found", + ) + except ValueError: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Control {control_id} not found in {framework}/{slug}/{version}", + ) + + return ControlRead( + control_id=control.get("control_id", ""), + title=control.get("title", ""), + description=control.get("description"), + severity=control.get("severity"), + service=control.get("service"), + data_collector_id=control.get("data_collector_id", ""), + policy_file=control.get("policy_file", ""), + requires_permissions=control.get("requires_permissions"), + ) diff --git a/backend-api/app/api/v1/evidence.py b/backend-api/app/api/v1/evidence.py index 5b5537d0..c68e98a4 100644 --- a/backend-api/app/api/v1/evidence.py +++ b/backend-api/app/api/v1/evidence.py @@ -16,8 +16,8 @@ def _find_security_dir() -> Path | None: SECURITY_DIR = _find_security_dir() -if SECURITY_DIR and str(SECURITY_DIR) not in sys.path: - sys.path.append(str(SECURITY_DIR)) +if SECURITY_DIR and str(SECURITY_DIR.parent) not in sys.path: + sys.path.insert(0, str(SECURITY_DIR.parent)) # Reuse existing evidence logic from security package from security.frontend import ui as evidence_ui diff --git a/backend-api/app/api/v1/m365_connections.py b/backend-api/app/api/v1/m365_connections.py new file mode 100644 index 00000000..59d9c856 --- /dev/null +++ b/backend-api/app/api/v1/m365_connections.py @@ -0,0 +1,170 @@ +"""M365 Connection API endpoints.""" + +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.auth import get_current_user +from app.db.session import get_async_session +from app.models.user import User +from app.models.m365_connection import M365Connection +from app.schemas.m365_connection import ( + M365ConnectionCreate, + M365ConnectionRead, + M365ConnectionUpdate, + M365ConnectionTestResult, +) +from app.services.encryption import encrypt, decrypt + +router = APIRouter(prefix="/m365-connections", tags=["M365 Connections"]) + + +@router.post("/", response_model=M365ConnectionRead, status_code=status.HTTP_201_CREATED) +async def create_connection( + connection_data: M365ConnectionCreate, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_async_session), +) -> M365Connection: + """Create a new M365 connection for the current user.""" + connection = M365Connection( + user_id=current_user.id, + name=connection_data.name, + tenant_id=connection_data.tenant_id, + client_id=connection_data.client_id, + encrypted_client_secret=encrypt(connection_data.client_secret), + ) + db.add(connection) + await db.commit() + await db.refresh(connection) + return connection + + +@router.get("/", response_model=list[M365ConnectionRead]) +async def list_connections( + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_async_session), +) -> list[M365Connection]: + """List all M365 connections for the current user.""" + result = await db.execute( + select(M365Connection) + .where(M365Connection.user_id == current_user.id) + .order_by(M365Connection.created_at.desc()) + ) + return list(result.scalars().all()) + + +@router.get("/{connection_id}", response_model=M365ConnectionRead) +async def get_connection( + connection_id: int, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_async_session), +) -> M365Connection: + """Get a specific M365 connection by ID.""" + result = await db.execute( + select(M365Connection).where( + M365Connection.id == connection_id, + M365Connection.user_id == current_user.id, + ) + ) + connection = result.scalar_one_or_none() + if not connection: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Connection {connection_id} not found", + ) + return connection + + +@router.put("/{connection_id}", response_model=M365ConnectionRead) +async def update_connection( + connection_id: int, + update_data: M365ConnectionUpdate, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_async_session), +) -> M365Connection: + """Update an M365 connection.""" + result = await db.execute( + select(M365Connection).where( + M365Connection.id == connection_id, + M365Connection.user_id == current_user.id, + ) + ) + connection = result.scalar_one_or_none() + if not connection: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Connection {connection_id} not found", + ) + + # Update only provided fields + if update_data.name is not None: + connection.name = update_data.name + if update_data.tenant_id is not None: + connection.tenant_id = update_data.tenant_id + if update_data.client_id is not None: + connection.client_id = update_data.client_id + if update_data.client_secret is not None: + connection.encrypted_client_secret = encrypt(update_data.client_secret) + if update_data.is_active is not None: + connection.is_active = update_data.is_active + + await db.commit() + await db.refresh(connection) + return connection + + +@router.delete("/{connection_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_connection( + connection_id: int, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_async_session), +) -> None: + """Delete an M365 connection (hard delete).""" + result = await db.execute( + select(M365Connection).where( + M365Connection.id == connection_id, + M365Connection.user_id == current_user.id, + ) + ) + connection = result.scalar_one_or_none() + if not connection: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Connection {connection_id} not found", + ) + + await db.delete(connection) + await db.commit() + + +@router.post("/{connection_id}/test", response_model=M365ConnectionTestResult) +async def test_connection( + connection_id: int, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_async_session), +) -> M365ConnectionTestResult: + """Test an M365 connection by attempting to authenticate.""" + result = await db.execute( + select(M365Connection).where( + M365Connection.id == connection_id, + M365Connection.user_id == current_user.id, + ) + ) + connection = result.scalar_one_or_none() + if not connection: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Connection {connection_id} not found", + ) + + # Decrypt credentials + client_secret = decrypt(connection.encrypted_client_secret) + + # TODO: Implement actual Graph API authentication test + # For now, return a placeholder response + # In production, use MSAL to get a token and verify it works + return M365ConnectionTestResult( + success=True, + message="Connection test not yet implemented - credentials decrypted successfully", + tenant_name=None, + ) diff --git a/backend-api/app/api/v1/platforms.py b/backend-api/app/api/v1/platforms.py new file mode 100644 index 00000000..15018666 --- /dev/null +++ b/backend-api/app/api/v1/platforms.py @@ -0,0 +1,41 @@ +"""Platform API endpoints for listing available platform types.""" + +from fastapi import APIRouter, Depends +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.auth import get_current_user +from app.db.session import get_async_session +from app.models.platform import Platform +from app.models.user import User +from app.schemas.platform import PlatformRead + +router = APIRouter(prefix="/platforms", tags=["Platforms"]) + + +@router.get("", response_model=list[PlatformRead]) +async def list_platforms( + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_async_session), +) -> list[PlatformRead]: + """List active platforms. + + Returns only platforms where is_active=True (currently only M365). + """ + result = await db.execute( + select(Platform).where(Platform.is_active == True).order_by(Platform.name) + ) + return list(result.scalars().all()) + + +@router.get("/all", response_model=list[PlatformRead]) +async def list_all_platforms( + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_async_session), +) -> list[PlatformRead]: + """List all platforms including inactive ones. + + Useful for showing which platforms will be supported in the future. + """ + result = await db.execute(select(Platform).order_by(Platform.name)) + return list(result.scalars().all()) diff --git a/backend-api/app/api/v1/router.py b/backend-api/app/api/v1/router.py index 63402d2b..c59a8758 100644 --- a/backend-api/app/api/v1/router.py +++ b/backend-api/app/api/v1/router.py @@ -1,5 +1,5 @@ from fastapi import APIRouter -from app.api.v1 import exports, audit, auth, test, evidence +from app.api.v1 import auth, test, evidence, m365_connections, scans, benchmarks, platforms api_router = APIRouter() @@ -9,7 +9,17 @@ # Test routes api_router.include_router(test.router) -# Existing routes -api_router.include_router(exports.router) -api_router.include_router(audit.router) +# Platform routes (list available platforms) +api_router.include_router(platforms.router) + +# Cloud connection routes +api_router.include_router(m365_connections.router) + +# Scan routes +api_router.include_router(scans.router) + +# Benchmark discovery routes +api_router.include_router(benchmarks.router) + +# Evidence routes api_router.include_router(evidence.router) diff --git a/backend-api/app/api/v1/scans.py b/backend-api/app/api/v1/scans.py new file mode 100644 index 00000000..aa1fa722 --- /dev/null +++ b/backend-api/app/api/v1/scans.py @@ -0,0 +1,185 @@ +"""Scan API endpoints.""" + +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import selectinload + +from app.core.auth import get_current_user +from app.db.session import get_async_session +from app.models.compliance import Scan +from app.models.m365_connection import M365Connection +from app.models.scan_result import ScanResult +from app.models.user import User +from app.schemas.scan import ( + ScanCreate, + ScanCreatedResponse, + ScanListItem, + ScanRead, + ScanResultRead, +) +from app.services.benchmark_reader import get_file_reader +from app.services.celery_client import queue_scan + +router = APIRouter(prefix="/scans", tags=["Scans"]) + + +@router.post("/", response_model=ScanCreatedResponse, status_code=status.HTTP_201_CREATED) +async def create_scan( + scan_data: ScanCreate, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_async_session), +) -> ScanCreatedResponse: + """Create a new compliance scan. + + Creates a scan record with all ScanResult records and queues a Celery task. + The scan runs asynchronously - poll GET /scans/{id} for status. + """ + # Verify the connection exists and belongs to the user + result = await db.execute( + select(M365Connection).where( + M365Connection.id == scan_data.m365_connection_id, + M365Connection.user_id == current_user.id, + M365Connection.is_active == True, + ) + ) + connection = result.scalar_one_or_none() + if not connection: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"M365 connection {scan_data.m365_connection_id} not found or inactive", + ) + + # Load benchmark metadata to get all controls + file_reader = get_file_reader() + try: + all_controls = file_reader.list_controls( + scan_data.framework, scan_data.benchmark, scan_data.version + ) + except FileNotFoundError: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Benchmark {scan_data.framework}/{scan_data.benchmark}/{scan_data.version} not found", + ) + + # Validate platform matches (benchmark must be for m365) + metadata = file_reader.get_benchmark_metadata( + scan_data.framework, scan_data.benchmark, scan_data.version + ) + if metadata.get("platform", "").lower() != "m365": + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Benchmark platform '{metadata.get('platform')}' does not match M365 connection", + ) + + # Create scan record + scan = Scan( + user_id=current_user.id, + m365_connection_id=scan_data.m365_connection_id, + framework=scan_data.framework, + benchmark=scan_data.benchmark, + version=scan_data.version, + status="pending", + total_controls=len(all_controls), + ) + db.add(scan) + await db.flush() # Get scan.id + + # Create ScanResult records for ALL controls + selected_ids = set(scan_data.control_ids) if scan_data.control_ids else None + skipped = 0 + for control in all_controls: + is_selected = selected_ids is None or control["control_id"] in selected_ids + result_status = "pending" if is_selected else "skipped" + if result_status == "skipped": + skipped += 1 + scan_result = ScanResult( + scan_id=scan.id, + control_id=control["control_id"], + status=result_status, + ) + db.add(scan_result) + + scan.skipped_count = skipped + await db.commit() + await db.refresh(scan) + + # Queue Celery task + task = queue_scan(scan.id) + + return ScanCreatedResponse( + id=scan.id, + status="pending", + message=f"Scan queued successfully. Task ID: {task.id}", + ) + + +@router.get("/", response_model=list[ScanListItem]) +async def list_scans( + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_async_session), + limit: int = 50, + offset: int = 0, +) -> list[Scan]: + """List scans for the current user.""" + result = await db.execute( + select(Scan) + .where(Scan.user_id == current_user.id) + .order_by(Scan.started_at.desc()) + .limit(limit) + .offset(offset) + ) + return list(result.scalars().all()) + + +@router.get("/{scan_id}", response_model=ScanRead) +async def get_scan( + scan_id: int, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_async_session), +) -> Scan: + """Get scan details by ID including results.""" + result = await db.execute( + select(Scan) + .options(selectinload(Scan.results)) + .where(Scan.id == scan_id, Scan.user_id == current_user.id) + ) + scan = result.scalar_one_or_none() + if not scan: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Scan {scan_id} not found", + ) + return scan + + +@router.get("/{scan_id}/results", response_model=list[ScanResultRead]) +async def get_scan_results( + scan_id: int, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_async_session), + status_filter: str | None = None, +) -> list[ScanResult]: + """Get results for a scan. + + Optionally filter by status (pending, passed, failed, error, skipped). + """ + # Verify scan exists and user has access + scan_result = await db.execute( + select(Scan).where(Scan.id == scan_id, Scan.user_id == current_user.id) + ) + scan = scan_result.scalar_one_or_none() + if not scan: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Scan {scan_id} not found", + ) + + # Build query + query = select(ScanResult).where(ScanResult.scan_id == scan_id) + if status_filter: + query = query.where(ScanResult.status == status_filter) + query = query.order_by(ScanResult.control_id) + + results = await db.execute(query) + return list(results.scalars().all()) diff --git a/backend-api/app/core/config.py b/backend-api/app/core/config.py index b378d686..7afca264 100644 --- a/backend-api/app/core/config.py +++ b/backend-api/app/core/config.py @@ -15,6 +15,19 @@ class Settings(BaseSettings): ALGORITHM: str = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 + # Redis (for Celery broker) + REDIS_URL: str = "redis://localhost:6379" + + # OPA (Open Policy Agent) + OPA_URL: str = "http://localhost:8181" + + # Encryption (for securing credentials at rest) + # Generate with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" + ENCRYPTION_KEY: str = "" + + # Policies directory (for benchmark/control metadata) + POLICIES_DIR: str = "/app/policies" + class Config: env_file = ".env" diff --git a/backend-api/app/main.py b/backend-api/app/main.py index c12ff353..1c8710a9 100644 --- a/backend-api/app/main.py +++ b/backend-api/app/main.py @@ -13,8 +13,12 @@ def create_app() -> FastAPI: setup_logging() app = FastAPI(title="AutoAudit API", version="0.1.0") + # RequestLoggingMiddleware must be added before CORSMiddleware + # (middleware executes in reverse order - last added runs first) + app.add_middleware(RequestLoggingMiddleware) + # Allow frontend (localhost:3000 and others) to call the API during development. - # Keep permissive for now; tighten via reverse proxy in production if needed. + # CORS must be added last so it runs first and wraps all responses including errors. app.add_middleware( CORSMiddleware, allow_origins=["*"], # permissive for dev; adjust in prod @@ -22,8 +26,6 @@ def create_app() -> FastAPI: allow_methods=["*"], allow_headers=["*"], ) - - app.add_middleware(RequestLoggingMiddleware) app.include_router(api_router, prefix=settings.API_PREFIX) # error handler diff --git a/backend-api/app/models/__init__.py b/backend-api/app/models/__init__.py index ef7db987..4f22ea67 100644 --- a/backend-api/app/models/__init__.py +++ b/backend-api/app/models/__init__.py @@ -1,12 +1,22 @@ """Database models for the AutoAudit backend API.""" + from app.models.user import User, Role -from app.models.compliance import Tenant, Rule, Scan, Issue +from app.models.m365_connection import M365Connection +from app.models.azure_connection import AzureConnection +from app.models.gcp_connection import GCPConnection +from app.models.aws_connection import AWSConnection +from app.models.platform import Platform +from app.models.scan_result import ScanResult +from app.models.compliance import Scan __all__ = [ "User", "Role", - "Tenant", - "Rule", + "M365Connection", + "AzureConnection", + "GCPConnection", + "AWSConnection", + "Platform", + "ScanResult", "Scan", - "Issue", ] diff --git a/backend-api/app/models/aws_connection.py b/backend-api/app/models/aws_connection.py new file mode 100644 index 00000000..08284b8c --- /dev/null +++ b/backend-api/app/models/aws_connection.py @@ -0,0 +1,13 @@ +"""AWS connection model stub.""" + +from sqlalchemy.orm import Mapped, mapped_column + +from app.db.base import Base + + +class AWSConnection(Base): + """AWS connection stub (columns added when implemented).""" + + __tablename__ = "aws_connection" + + id: Mapped[int] = mapped_column(primary_key=True) diff --git a/backend-api/app/models/azure_connection.py b/backend-api/app/models/azure_connection.py new file mode 100644 index 00000000..5f6f0651 --- /dev/null +++ b/backend-api/app/models/azure_connection.py @@ -0,0 +1,13 @@ +"""Azure connection model stub.""" + +from sqlalchemy.orm import Mapped, mapped_column + +from app.db.base import Base + + +class AzureConnection(Base): + """Azure connection stub (columns added when implemented).""" + + __tablename__ = "azure_connection" + + id: Mapped[int] = mapped_column(primary_key=True) diff --git a/backend-api/app/models/compliance.py b/backend-api/app/models/compliance.py index 03fb7e59..4b369d4b 100644 --- a/backend-api/app/models/compliance.py +++ b/backend-api/app/models/compliance.py @@ -1,79 +1,70 @@ -"""Compliance models for audit scans and findings.""" +"""Compliance models for audit scans.""" + from datetime import datetime from decimal import Decimal -from typing import Optional +from typing import TYPE_CHECKING, Optional -from sqlalchemy import ForeignKey, String, Text, Numeric -from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy import ForeignKey, Numeric, String, Text from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.sql import func from app.db.base import Base - -class Tenant(Base): - """Tenant represents a company or organization being audited.""" - __tablename__ = "tenant" - - id: Mapped[int] = mapped_column(primary_key=True) - name: Mapped[str] = mapped_column(String(255), nullable=False) - external_tenant_id: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) - created_at: Mapped[datetime] = mapped_column(server_default=func.now()) - - # Relationships - scans: Mapped[list["Scan"]] = relationship(back_populates="tenant") - - -class Rule(Base): - """Rule represents a compliance control from a framework (e.g., CIS, Essential 8).""" - __tablename__ = "rule" - - id: Mapped[int] = mapped_column(primary_key=True) - framework: Mapped[str] = mapped_column(String(100), nullable=False) - control_key: Mapped[str] = mapped_column(String(100), nullable=False) - title: Mapped[str] = mapped_column(Text, nullable=False) - severity: Mapped[Optional[str]] = mapped_column(String(20), nullable=True) - description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) - - # Relationships - issues: Mapped[list["Issue"]] = relationship(back_populates="rule") +if TYPE_CHECKING: + from app.models.m365_connection import M365Connection + from app.models.scan_result import ScanResult + from app.models.user import User class Scan(Base): - """Scan represents an individual compliance scan run against a tenant.""" + """Scan represents an individual compliance scan run against a cloud connection.""" + __tablename__ = "scan" id: Mapped[int] = mapped_column(primary_key=True) - tenant_id: Mapped[int] = mapped_column(ForeignKey("tenant.id"), nullable=False) + + # Direct user ownership + user_id: Mapped[int] = mapped_column(ForeignKey("user.id"), nullable=False) + + # Cloud connection FKs (only one should be non-null per scan) + m365_connection_id: Mapped[Optional[int]] = mapped_column( + ForeignKey("m365_connection.id"), nullable=True + ) + azure_connection_id: Mapped[Optional[int]] = mapped_column( + ForeignKey("azure_connection.id"), nullable=True + ) + gcp_connection_id: Mapped[Optional[int]] = mapped_column( + ForeignKey("gcp_connection.id"), nullable=True + ) + aws_connection_id: Mapped[Optional[int]] = mapped_column( + ForeignKey("aws_connection.id"), nullable=True + ) + + # Framework/benchmark targeting + framework: Mapped[str] = mapped_column(String(50), nullable=False) + benchmark: Mapped[str] = mapped_column(String(100), nullable=False) + version: Mapped[str] = mapped_column(String(20), nullable=False) + # Note: control_ids removed - ScanResult records track which controls were selected + + # Scan timing and status started_at: Mapped[datetime] = mapped_column(server_default=func.now()) finished_at: Mapped[Optional[datetime]] = mapped_column(nullable=True) - status: Mapped[str] = mapped_column(String(30), default="running") - compliance_score: Mapped[Optional[Decimal]] = mapped_column(Numeric(5, 2), nullable=True) + status: Mapped[str] = mapped_column(String(30), default="pending") + + # Results summary + compliance_score: Mapped[Optional[Decimal]] = mapped_column( + Numeric(5, 2), nullable=True + ) total_controls: Mapped[int] = mapped_column(default=0) passed_count: Mapped[int] = mapped_column(default=0) failed_count: Mapped[int] = mapped_column(default=0) - not_tested_count: Mapped[int] = mapped_column(default=0) + skipped_count: Mapped[int] = mapped_column(default=0) + error_count: Mapped[int] = mapped_column(default=0) notes: Mapped[Optional[str]] = mapped_column(Text, nullable=True) # Relationships - tenant: Mapped["Tenant"] = relationship(back_populates="scans") - issues: Mapped[list["Issue"]] = relationship(back_populates="scan") - - -class Issue(Base): - """Issue represents a finding from a compliance scan.""" - __tablename__ = "issue" - - id: Mapped[int] = mapped_column(primary_key=True) - scan_id: Mapped[int] = mapped_column(ForeignKey("scan.id"), nullable=False) - rule_id: Mapped[Optional[int]] = mapped_column(ForeignKey("rule.id"), nullable=True) - priority: Mapped[Optional[str]] = mapped_column(String(20), nullable=True) - title: Mapped[Optional[str]] = mapped_column(Text, nullable=True) - description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) - result: Mapped[Optional[str]] = mapped_column(String(20), nullable=True) - evidence: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True) - created_at: Mapped[datetime] = mapped_column(server_default=func.now()) - - # Relationships - scan: Mapped["Scan"] = relationship(back_populates="issues") - rule: Mapped[Optional["Rule"]] = relationship(back_populates="issues") + user: Mapped["User"] = relationship(back_populates="scans") + m365_connection: Mapped[Optional["M365Connection"]] = relationship( + back_populates="scans" + ) + results: Mapped[list["ScanResult"]] = relationship(back_populates="scan") diff --git a/backend-api/app/models/gcp_connection.py b/backend-api/app/models/gcp_connection.py new file mode 100644 index 00000000..508cc378 --- /dev/null +++ b/backend-api/app/models/gcp_connection.py @@ -0,0 +1,13 @@ +"""GCP connection model stub.""" + +from sqlalchemy.orm import Mapped, mapped_column + +from app.db.base import Base + + +class GCPConnection(Base): + """GCP connection stub (columns added when implemented).""" + + __tablename__ = "gcp_connection" + + id: Mapped[int] = mapped_column(primary_key=True) diff --git a/backend-api/app/models/m365_connection.py b/backend-api/app/models/m365_connection.py new file mode 100644 index 00000000..aa05afd6 --- /dev/null +++ b/backend-api/app/models/m365_connection.py @@ -0,0 +1,51 @@ +"""M365 Connection model for storing Microsoft 365 tenant credentials.""" + +from datetime import datetime +from typing import TYPE_CHECKING + +from sqlalchemy import ForeignKey, String, Text, Boolean +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.sql import func + +from app.db.base import Base + +if TYPE_CHECKING: + from app.models.user import User + from app.models.compliance import Scan + + +class M365Connection(Base): + """M365Connection stores credentials for connecting to a Microsoft 365 tenant. + + The client_secret is encrypted at rest using Fernet symmetric encryption. + Users can have multiple M365 connections (e.g., for different clients/tenants). + """ + + __tablename__ = "m365_connection" + + id: Mapped[int] = mapped_column(primary_key=True) + user_id: Mapped[int] = mapped_column(ForeignKey("user.id"), nullable=False) + + # Human-readable name for this connection (e.g., "Contoso Production") + name: Mapped[str] = mapped_column(String(255), nullable=False) + + # Azure AD / Entra ID tenant GUID + tenant_id: Mapped[str] = mapped_column(String(255), nullable=False) + + # Azure AD App Registration client ID + client_id: Mapped[str] = mapped_column(String(255), nullable=False) + + # Encrypted client secret (Fernet encryption) + encrypted_client_secret: Mapped[str] = mapped_column(Text, nullable=False) + + # Soft active flag - allows deactivating without deleting + is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) + + created_at: Mapped[datetime] = mapped_column(server_default=func.now()) + updated_at: Mapped[datetime] = mapped_column( + server_default=func.now(), onupdate=func.now() + ) + + # Relationships + user: Mapped["User"] = relationship(back_populates="m365_connections") + scans: Mapped[list["Scan"]] = relationship(back_populates="m365_connection") diff --git a/backend-api/app/models/platform.py b/backend-api/app/models/platform.py new file mode 100644 index 00000000..97cfcbec --- /dev/null +++ b/backend-api/app/models/platform.py @@ -0,0 +1,17 @@ +"""Platform model for supported platform types.""" + +from sqlalchemy import Boolean, String +from sqlalchemy.orm import Mapped, mapped_column + +from app.db.base import Base + + +class Platform(Base): + """Lookup table for supported platform types (M365, Azure, GCP, AWS).""" + + __tablename__ = "platform" + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String(50), unique=True, nullable=False) + display_name: Mapped[str] = mapped_column(String(100), nullable=False) + is_active: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) diff --git a/backend-api/app/models/scan_result.py b/backend-api/app/models/scan_result.py new file mode 100644 index 00000000..23be59f1 --- /dev/null +++ b/backend-api/app/models/scan_result.py @@ -0,0 +1,40 @@ +"""ScanResult model for individual control results within a scan.""" + +from datetime import datetime +from typing import TYPE_CHECKING, Optional + +from sqlalchemy import ForeignKey, String, Text, UniqueConstraint +from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.sql import func + +from app.db.base import Base + +if TYPE_CHECKING: + from app.models.compliance import Scan + + +class ScanResult(Base): + """Individual control result within a scan.""" + + __tablename__ = "scan_result" + __table_args__ = ( + UniqueConstraint("scan_id", "control_id", name="uq_scan_result_scan_control"), + ) + + id: Mapped[int] = mapped_column(primary_key=True) + scan_id: Mapped[int] = mapped_column(ForeignKey("scan.id"), nullable=False) + control_id: Mapped[str] = mapped_column(String(50), nullable=False) + + # Status: pending, passed, failed, error, skipped + status: Mapped[str] = mapped_column(String(20), nullable=False) + message: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + evidence: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True) + + created_at: Mapped[datetime] = mapped_column(server_default=func.now()) + updated_at: Mapped[datetime] = mapped_column( + server_default=func.now(), onupdate=func.now() + ) + + # Relationships + scan: Mapped["Scan"] = relationship(back_populates="results") diff --git a/backend-api/app/models/user.py b/backend-api/app/models/user.py index 23944571..1317f074 100644 --- a/backend-api/app/models/user.py +++ b/backend-api/app/models/user.py @@ -1,9 +1,16 @@ from enum import Enum +from typing import TYPE_CHECKING + from fastapi_users.db import SQLAlchemyBaseUserTable from sqlalchemy import String -from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy.orm import Mapped, mapped_column, relationship + from app.db.base import Base +if TYPE_CHECKING: + from app.models.compliance import Scan + from app.models.m365_connection import M365Connection + class Role(str, Enum): """User roles for role-based access control.""" @@ -32,3 +39,9 @@ class User(SQLAlchemyBaseUserTable[int], Base): # - is_active: bool # - is_superuser: bool # - is_verified: bool + + # Relationships + m365_connections: Mapped[list["M365Connection"]] = relationship( + back_populates="user" + ) + scans: Mapped[list["Scan"]] = relationship(back_populates="user") diff --git a/backend-api/app/schemas/__init__.py b/backend-api/app/schemas/__init__.py index fe02af45..96478d47 100644 --- a/backend-api/app/schemas/__init__.py +++ b/backend-api/app/schemas/__init__.py @@ -1,14 +1,8 @@ """Pydantic schemas for request/response validation.""" from app.schemas.user import UserRead, UserCreate, UserUpdate -from app.schemas.audit import AuditLog -from app.schemas.exports import ExportRequest, ExportResponse, ExportStatusResponse __all__ = [ "UserRead", "UserCreate", "UserUpdate", - "AuditLog", - "ExportRequest", - "ExportResponse", - "ExportStatusResponse", ] diff --git a/backend-api/app/schemas/benchmark.py b/backend-api/app/schemas/benchmark.py new file mode 100644 index 00000000..5d9fdf2a --- /dev/null +++ b/backend-api/app/schemas/benchmark.py @@ -0,0 +1,29 @@ +"""Benchmark and control schemas for API responses.""" + +from pydantic import BaseModel + + +class BenchmarkRead(BaseModel): + """Schema for benchmark data read from metadata.json files.""" + + framework: str + slug: str + version: str + name: str + platform: str + release_date: str | None = None + source_url: str | None = None + control_count: int + + +class ControlRead(BaseModel): + """Schema for control data read from metadata.json files.""" + + control_id: str + title: str + description: str | None = None + severity: str | None = None + service: str | None = None + data_collector_id: str + policy_file: str + requires_permissions: list[str] | None = None diff --git a/backend-api/app/schemas/m365_connection.py b/backend-api/app/schemas/m365_connection.py new file mode 100644 index 00000000..4917aa17 --- /dev/null +++ b/backend-api/app/schemas/m365_connection.py @@ -0,0 +1,50 @@ +"""Pydantic schemas for M365 connections.""" + +from datetime import datetime + +from pydantic import BaseModel, Field + + +class M365ConnectionBase(BaseModel): + """Base schema for M365 connection.""" + + name: str = Field(..., min_length=1, max_length=255, description="Friendly name for this connection") + tenant_id: str = Field(..., min_length=1, max_length=255, description="Azure AD tenant GUID") + client_id: str = Field(..., min_length=1, max_length=255, description="App registration client ID") + + +class M365ConnectionCreate(M365ConnectionBase): + """Schema for creating an M365 connection.""" + + client_secret: str = Field(..., min_length=1, description="App registration client secret") + + +class M365ConnectionUpdate(BaseModel): + """Schema for updating an M365 connection.""" + + name: str | None = Field(None, min_length=1, max_length=255) + tenant_id: str | None = Field(None, min_length=1, max_length=255) + client_id: str | None = Field(None, min_length=1, max_length=255) + client_secret: str | None = Field(None, min_length=1, description="New client secret (if changing)") + is_active: bool | None = None + + +class M365ConnectionRead(M365ConnectionBase): + """Schema for reading an M365 connection (without secret).""" + + id: int + user_id: int + is_active: bool + created_at: datetime + updated_at: datetime + + class Config: + from_attributes = True + + +class M365ConnectionTestResult(BaseModel): + """Schema for connection test result.""" + + success: bool + message: str + tenant_name: str | None = None diff --git a/backend-api/app/schemas/platform.py b/backend-api/app/schemas/platform.py new file mode 100644 index 00000000..45579819 --- /dev/null +++ b/backend-api/app/schemas/platform.py @@ -0,0 +1,14 @@ +"""Platform schemas for API responses.""" + +from pydantic import BaseModel, ConfigDict + + +class PlatformRead(BaseModel): + """Schema for platform data.""" + + id: int + name: str + display_name: str + is_active: bool + + model_config = ConfigDict(from_attributes=True) diff --git a/backend-api/app/schemas/scan.py b/backend-api/app/schemas/scan.py new file mode 100644 index 00000000..44bb0a08 --- /dev/null +++ b/backend-api/app/schemas/scan.py @@ -0,0 +1,101 @@ +"""Pydantic schemas for compliance scans.""" + +from datetime import datetime +from decimal import Decimal + +from pydantic import BaseModel, ConfigDict, Field + + +class ScanCreate(BaseModel): + """Schema for creating a new scan.""" + + m365_connection_id: int = Field( + ..., description="ID of the M365 connection to scan" + ) + framework: str = Field( + ..., min_length=1, max_length=50, description="Framework (e.g., 'cis')" + ) + benchmark: str = Field( + ..., + min_length=1, + max_length=100, + description="Benchmark slug (e.g., 'microsoft-365-foundations')", + ) + version: str = Field( + ..., min_length=1, max_length=20, description="Version (e.g., 'v3.1.0')" + ) + control_ids: list[str] | None = Field( + None, description="Specific control IDs to scan (null = all)" + ) + + +class ScanResultRead(BaseModel): + """Schema for reading scan result details.""" + + id: int + scan_id: int + control_id: str + status: str # pending, passed, failed, error, skipped + message: str | None + evidence: dict | None + created_at: datetime + updated_at: datetime + + model_config = ConfigDict(from_attributes=True) + + +class ScanRead(BaseModel): + """Schema for reading scan details.""" + + id: int + user_id: int + m365_connection_id: int | None + azure_connection_id: int | None + gcp_connection_id: int | None + aws_connection_id: int | None + framework: str + benchmark: str + version: str + status: str + started_at: datetime + finished_at: datetime | None + compliance_score: Decimal | None + total_controls: int + passed_count: int + failed_count: int + skipped_count: int + error_count: int + notes: str | None + results: list[ScanResultRead] | None = None + + model_config = ConfigDict(from_attributes=True) + + +class ScanListItem(BaseModel): + """Schema for scan list items (abbreviated).""" + + id: int + user_id: int + m365_connection_id: int | None + framework: str + benchmark: str + version: str + status: str + started_at: datetime + finished_at: datetime | None + compliance_score: Decimal | None + total_controls: int + passed_count: int + failed_count: int + skipped_count: int + error_count: int + + model_config = ConfigDict(from_attributes=True) + + +class ScanCreatedResponse(BaseModel): + """Response schema for scan creation.""" + + id: int + status: str + message: str diff --git a/backend-api/app/services/benchmark_reader.py b/backend-api/app/services/benchmark_reader.py new file mode 100644 index 00000000..7d21fc57 --- /dev/null +++ b/backend-api/app/services/benchmark_reader.py @@ -0,0 +1,123 @@ +"""Read benchmark and control metadata from policy files at runtime. + +Policies are organized as: + policies/{framework}/{benchmark-slug}/{version}/metadata.json + policies/{framework}/{benchmark-slug}/{version}/*.rego + +Example: + policies/cis/microsoft-365-foundations/v3.1.0/metadata.json +""" + +from __future__ import annotations + +import json +from functools import lru_cache +from pathlib import Path +from typing import Any + +from app.core.config import get_settings + + +class BenchmarkFileReader: + """Read benchmark and control metadata from files at runtime.""" + + def __init__(self, policies_dir: Path | str | None = None): + if policies_dir is None: + settings = get_settings() + policies_dir = getattr(settings, "POLICIES_DIR", "/app/policies") + self.policies_dir = Path(policies_dir) + + def get_benchmark_path(self, framework: str, slug: str, version: str) -> Path: + """Get the path to a benchmark's directory.""" + return self.policies_dir / framework / slug / version + + def get_benchmark_metadata(self, framework: str, slug: str, version: str) -> dict[str, Any]: + """Read metadata.json for a benchmark. + + Args: + framework: e.g., "cis" + slug: e.g., "microsoft-365-foundations" + version: e.g., "v3.1.0" + + Returns: + The full metadata dict including benchmark info and controls. + + Raises: + FileNotFoundError: If metadata.json doesn't exist. + """ + path = self.get_benchmark_path(framework, slug, version) / "metadata.json" + if not path.exists(): + raise FileNotFoundError(f"Metadata not found: {path}") + return json.loads(path.read_text(encoding="utf-8")) + + def get_control_metadata( + self, framework: str, slug: str, version: str, control_id: str + ) -> dict[str, Any]: + """Get control details from metadata.json. + + Args: + framework: e.g., "cis" + slug: e.g., "microsoft-365-foundations" + version: e.g., "v3.1.0" + control_id: e.g., "CIS-1.1.1" + + Returns: + Control metadata dict. + + Raises: + ValueError: If control is not found. + FileNotFoundError: If metadata.json doesn't exist. + """ + metadata = self.get_benchmark_metadata(framework, slug, version) + for control in metadata.get("controls", []): + if control.get("control_id") == control_id: + return control + raise ValueError(f"Control {control_id} not found in {framework}/{slug}/{version}") + + def list_benchmarks(self) -> list[dict[str, Any]]: + """List all available benchmarks by scanning the policies directory. + + Returns: + List of benchmark metadata dicts. + """ + benchmarks = [] + if not self.policies_dir.exists(): + return benchmarks + + for framework_dir in self.policies_dir.iterdir(): + if not framework_dir.is_dir(): + continue + for benchmark_dir in framework_dir.iterdir(): + if not benchmark_dir.is_dir(): + continue + for version_dir in benchmark_dir.iterdir(): + if not version_dir.is_dir(): + continue + metadata_file = version_dir / "metadata.json" + if metadata_file.exists(): + try: + metadata = json.loads(metadata_file.read_text(encoding="utf-8")) + benchmarks.append(metadata) + except (json.JSONDecodeError, OSError): + pass + return benchmarks + + def list_controls(self, framework: str, slug: str, version: str) -> list[dict[str, Any]]: + """List all controls for a specific benchmark. + + Returns: + List of control metadata dicts. + """ + metadata = self.get_benchmark_metadata(framework, slug, version) + return metadata.get("controls", []) + + def benchmark_exists(self, framework: str, slug: str, version: str) -> bool: + """Check if a benchmark exists on disk.""" + path = self.get_benchmark_path(framework, slug, version) / "metadata.json" + return path.exists() + + +@lru_cache(maxsize=1) +def get_file_reader() -> BenchmarkFileReader: + """Get a cached file reader instance.""" + return BenchmarkFileReader() diff --git a/backend-api/app/services/celery_client.py b/backend-api/app/services/celery_client.py new file mode 100644 index 00000000..bb4ce644 --- /dev/null +++ b/backend-api/app/services/celery_client.py @@ -0,0 +1,57 @@ +"""Celery client for queueing tasks from the backend API.""" + +from celery import Celery +from celery.result import AsyncResult + +from app.core.config import get_settings + +settings = get_settings() + +# Create Celery app connected to the same broker as the worker +celery_app = Celery( + "autoaudit", + broker=settings.REDIS_URL, +) + +# Task routing +celery_app.conf.update( + task_serializer="json", + accept_content=["json"], + result_serializer="json", + task_default_queue="autoaudit", +) + + +def queue_scan(scan_id: int) -> AsyncResult: + """Queue a scan task for execution by the worker. + + Args: + scan_id: The scan ID to process + + Returns: + Celery AsyncResult for tracking task status + """ + return celery_app.send_task( + "worker.tasks.run_scan", + args=[scan_id], + queue="autoaudit", + ) + + +def get_task_status(task_id: str) -> dict: + """Get the status of a task. + + Args: + task_id: The Celery task ID + + Returns: + Dict with task status info + """ + result = AsyncResult(task_id, app=celery_app) + return { + "task_id": task_id, + "status": result.status, + "ready": result.ready(), + "successful": result.successful() if result.ready() else None, + "result": result.result if result.ready() else None, + } diff --git a/backend-api/app/services/encryption.py b/backend-api/app/services/encryption.py new file mode 100644 index 00000000..174028d9 --- /dev/null +++ b/backend-api/app/services/encryption.py @@ -0,0 +1,58 @@ +"""Encryption service for securing sensitive data at rest. + +Uses Fernet symmetric encryption. The encryption key must be a 32-byte +base64-encoded key, provided via the ENCRYPTION_KEY environment variable. + +Generate a key with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" +""" + +from cryptography.fernet import Fernet, InvalidToken + +from app.core.config import get_settings + +_fernet: Fernet | None = None + + +def get_fernet() -> Fernet: + """Get or create the Fernet encryption instance.""" + global _fernet + if _fernet is None: + settings = get_settings() + if not settings.ENCRYPTION_KEY: + raise ValueError( + "ENCRYPTION_KEY environment variable is required. " + "Generate one with: python -c \"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())\"" + ) + _fernet = Fernet(settings.ENCRYPTION_KEY.encode()) + return _fernet + + +def encrypt(plaintext: str) -> str: + """Encrypt a string and return the ciphertext as a string. + + Args: + plaintext: The string to encrypt. + + Returns: + The encrypted string (base64-encoded). + """ + if not plaintext: + return "" + return get_fernet().encrypt(plaintext.encode()).decode() + + +def decrypt(ciphertext: str) -> str: + """Decrypt a ciphertext string and return the plaintext. + + Args: + ciphertext: The encrypted string (base64-encoded). + + Returns: + The decrypted plaintext string. + + Raises: + InvalidToken: If the ciphertext is invalid or the key is wrong. + """ + if not ciphertext: + return "" + return get_fernet().decrypt(ciphertext.encode()).decode() diff --git a/backend-api/pyproject.toml b/backend-api/pyproject.toml index 79977038..c2f4f1f0 100644 --- a/backend-api/pyproject.toml +++ b/backend-api/pyproject.toml @@ -16,6 +16,10 @@ dependencies = [ "alembic>=1.13.0", # Multipart upload support for evidence/file endpoints "python-multipart>=0.0.12", + # Encryption for credentials at rest + "cryptography>=42.0.0", + # Celery client for queueing tasks + "celery[redis]>=5.3.0", ] [project.optional-dependencies] diff --git a/backend-api/uv.lock b/backend-api/uv.lock index 04a1dd00..c92f1ea7 100644 --- a/backend-api/uv.lock +++ b/backend-api/uv.lock @@ -21,6 +21,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ba/88/6237e97e3385b57b5f1528647addea5cc03d4d65d5979ab24327d41fb00d/alembic-1.17.2-py3-none-any.whl", hash = "sha256:f483dd1fe93f6c5d49217055e4d15b905b425b6af906746abb35b69c1996c4e6", size = 248554, upload-time = "2025-11-14T20:35:05.699Z" }, ] +[[package]] +name = "amqp" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013, upload-time = "2024-11-12T19:55:44.051Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944, upload-time = "2024-11-12T19:55:41.782Z" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -173,6 +185,8 @@ source = { virtual = "." } dependencies = [ { name = "alembic" }, { name = "asyncpg" }, + { name = "celery", extra = ["redis"] }, + { name = "cryptography" }, { name = "fastapi" }, { name = "fastapi-users", extra = ["sqlalchemy"] }, { name = "pydantic" }, @@ -199,6 +213,8 @@ evidence = [ requires-dist = [ { name = "alembic", specifier = ">=1.13.0" }, { name = "asyncpg", specifier = ">=0.29.0" }, + { name = "celery", extras = ["redis"], specifier = ">=5.3.0" }, + { name = "cryptography", specifier = ">=42.0.0" }, { name = "docx2pdf", marker = "extra == 'evidence'", specifier = ">=0.1.8" }, { name = "fastapi", specifier = ">=0.116.1" }, { name = "fastapi-users", extras = ["sqlalchemy"], specifier = ">=13.0.0" }, @@ -276,6 +292,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/13/47bba97924ebe86a62ef83dc75b7c8a881d53c535f83e2c54c4bd701e05c/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57967b7a28d855313a963aaea51bf6df89f833db4320da458e5b3c5ab6d4c938", size = 280110, upload-time = "2025-02-28T01:24:05.896Z" }, ] +[[package]] +name = "billiard" +version = "4.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/23/b12ac0bcdfb7360d664f40a00b1bda139cbbbced012c34e375506dbd0143/billiard-4.2.4.tar.gz", hash = "sha256:55f542c371209e03cd5862299b74e52e4fbcba8250ba611ad94276b369b6a85f", size = 156537, upload-time = "2025-11-30T13:28:48.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/87/8bab77b323f16d67be364031220069f79159117dd5e43eeb4be2fef1ac9b/billiard-4.2.4-py3-none-any.whl", hash = "sha256:525b42bdec68d2b983347ac312f892db930858495db601b5836ac24e6477cde5", size = 87070, upload-time = "2025-11-30T13:28:47.016Z" }, +] + +[[package]] +name = "celery" +version = "5.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "billiard" }, + { name = "click" }, + { name = "click-didyoumean" }, + { name = "click-plugins" }, + { name = "click-repl" }, + { name = "exceptiongroup" }, + { name = "kombu" }, + { name = "python-dateutil" }, + { name = "tzlocal" }, + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/5f/b681ae3c89290d2ea6562ea96b40f5af6f6fc5f7743e2cd1a19e47721548/celery-5.6.0.tar.gz", hash = "sha256:641405206042d52ae460e4e9751a2e31b06cf80ab836fcf92e0b9311d7ea8113", size = 1712522, upload-time = "2025-11-30T17:39:46.282Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/4e/53a125038d6a814491a0ae3457435c13cf8821eb602292cf9db37ce35f62/celery-5.6.0-py3-none-any.whl", hash = "sha256:33cf01477b175017fc8f22c5ee8a65157591043ba8ca78a443fe703aa910f581", size = 444561, upload-time = "2025-11-30T17:39:44.314Z" }, +] + +[package.optional-dependencies] +redis = [ + { name = "kombu", extra = ["redis"] }, +] + [[package]] name = "cffi" version = "2.0.0" @@ -370,6 +421,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, ] +[[package]] +name = "click-didyoumean" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089, upload-time = "2024-03-24T08:22:07.499Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631, upload-time = "2024-03-24T08:22:06.356Z" }, +] + +[[package]] +name = "click-plugins" +version = "1.1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261", size = 8343, upload-time = "2025-06-25T00:47:37.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6", size = 11051, upload-time = "2025-06-25T00:47:36.731Z" }, +] + +[[package]] +name = "click-repl" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "prompt-toolkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449, upload-time = "2023-06-15T12:43:51.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289, upload-time = "2023-06-15T12:43:48.626Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -700,6 +788,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] +[[package]] +name = "kombu" +version = "5.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "amqp" }, + { name = "packaging" }, + { name = "tzdata" }, + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/05/749ada8e51718445d915af13f1d18bc4333848e8faa0cb234028a3328ec8/kombu-5.6.1.tar.gz", hash = "sha256:90f1febb57ad4f53ca327a87598191b2520e0c793c75ea3b88d98e3b111282e4", size = 471548, upload-time = "2025-11-25T11:07:33.504Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/d6/943cf84117cd9ddecf6e1707a3f712a49fc64abdb8ac31b19132871af1dd/kombu-5.6.1-py3-none-any.whl", hash = "sha256:b69e3f5527ec32fc5196028a36376501682973e9620d6175d1c3d4eaf7e95409", size = 214141, upload-time = "2025-11-25T11:07:31.54Z" }, +] + +[package.optional-dependencies] +redis = [ + { name = "redis" }, +] + [[package]] name = "lxml" version = "6.0.2" @@ -1116,6 +1224,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850, upload-time = "2025-10-15T18:24:11.495Z" }, ] +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + [[package]] name = "pwdlib" version = "0.2.1" @@ -1299,6 +1419,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7a/33/8312d7ce74670c9d39a532b2c246a853861120486be9443eebf048043637/pytesseract-0.3.13-py3-none-any.whl", hash = "sha256:7a99c6c2ac598360693d83a416e36e0b33a67638bb9d77fdcac094a3589d4b34", size = 14705, upload-time = "2024-08-16T02:36:10.09Z" }, ] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + [[package]] name = "python-docx" version = "1.2.0" @@ -1352,6 +1484,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, ] +[[package]] +name = "redis" +version = "6.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-timeout", marker = "python_full_version < '3.11.3'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/d6/e8b92798a5bd67d659d51a18170e91c16ac3b59738d91894651ee255ed49/redis-6.4.0.tar.gz", hash = "sha256:b01bc7282b8444e28ec36b261df5375183bb47a07eb9c603f284e89cbc5ef010", size = 4647399, upload-time = "2025-08-07T08:10:11.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/02/89e2ed7e85db6c93dfa9e8f691c5087df4e3551ab39081a4d7c6d1f90e05/redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f", size = 279847, upload-time = "2025-08-07T08:10:09.84Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -1515,6 +1668,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, ] +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "tzlocal" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, +] + [[package]] name = "uvicorn" version = "0.35.0" @@ -1528,3 +1702,21 @@ sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8 wheels = [ { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, ] + +[[package]] +name = "vine" +version = "5.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980, upload-time = "2023-11-05T08:46:53.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636, upload-time = "2023-11-05T08:46:51.205Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, +] From a8a1dd555992dd6a0b3d98c91bf2a94c7691ed72 Mon Sep 17 00:00:00 2001 From: David Hartley Date: Fri, 12 Dec 2025 11:45:08 +1100 Subject: [PATCH 027/111] Update readme and contributing documents for clarity --- README.md | 5 ++ docker-compose.yml | 6 +- docs/CONTRIBUTING.md | 176 +++++++++++++++++++++++++++++++++++++++ docs/GETTING_STARTED.md | 178 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 362 insertions(+), 3 deletions(-) create mode 100644 docs/CONTRIBUTING.md create mode 100644 docs/GETTING_STARTED.md diff --git a/README.md b/README.md index 33f5f7fa..906978bc 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,11 @@ ## Project Overview AutoAudit is a M365 compliance automation platform built by several specialist teams. This monorepo centralizes all codebases—including backend services, APIs, compliance scanners, and frontends—enabling unified CI/CD, streamlined development, and rapid automated deployments to the cloud. +## Documentation + +- [Getting Started](docs/GETTING_STARTED.md) - Set up your development environment +- [Contributing Guide](docs/CONTRIBUTING.md) - Find where to contribute based on your skills + ## Repository Structure The repo follows the established modular structure: - `/backend-api` diff --git a/docker-compose.yml b/docker-compose.yml index 73409914..f32c0969 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,9 @@ # AutoAudit Development Docker Compose # # Usage with profiles: -# docker compose up # Start only db and redis -# docker compose --profile frontend-dev up # For frontend devs (starts backend-api, db, redis) -# docker compose --profile backend-dev up # For backend devs (starts frontend, db, redis) +# docker compose up # Start infrastructure only (db, redis, opa) +# docker compose --profile frontend-dev up # For frontend devs (starts backend-api + infrastructure) +# docker compose --profile backend-dev up # For backend devs (starts frontend + infrastructure) # docker compose --profile all up # Start all services # # To run in detached mode, add -d flag: diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 00000000..b03ec18b --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,176 @@ +# Contributing to AutoAudit + +This guide helps you find the right area to contribute based on your skills and interests. AutoAudit is a monorepo with several modules, and you don't necessarily need to understand the whole system to make meaningful contributions. + +## Project Overview + +AutoAudit is a compliance automation platform for cloud environments. It collects configuration data from cloud services, evaluates it against compliance frameworks (like CIS benchmarks), and presents findings through a dashboard. The platform is built around these core components: + +- A REST API that handles authentication and orchestrates scans +- A scanning engine that collects data and evaluates compliance policies +- A frontend dashboard for viewing results +- Infrastructure for deployment and monitoring + +## Module Guide + +### Backend API + +**Location**: `/backend-api` + +**What it does**: The backend API is the central hub of the application. It handles user authentication, manages tenants and scans, serves compliance results to the frontend, and coordinates with the scanning engine. + +**Tech stack**: +- Python 3.10+ +- FastAPI with Pydantic for request/response validation +- SQLAlchemy 2.0+ with async PostgreSQL +- FastAPI-Users for JWT authentication +- Alembic for database migrations + +**Good fit if you**: +- Have Python experience and want to work on web APIs +- Are interested in authentication systems and RBAC +- Want to learn FastAPI and modern Python async patterns +- Enjoy designing database schemas and writing migrations + +**Key directories**: +- `app/api/v1/` - API endpoint handlers +- `app/models/` - SQLAlchemy database models +- `app/schemas/` - Pydantic request/response schemas +- `app/services/` - Business logic layer +- `app/core/` - Configuration, auth, and middleware + +**Getting started**: See `backend-api/README.md` for setup instructions and examples of securing endpoints. + +--- + +### Frontend + +**Location**: `/frontend` + +**What it does**: The frontend is a React dashboard that displays compliance scan results, visualizes data through charts, and provides the user interface for managing scans and settings. + +**Tech stack**: +- React 19 +- Tailwind CSS for styling +- React Router for navigation +- Chart.js for data visualization + +**Good fit if you**: +- Have JavaScript/TypeScript and React experience +- Are interested in data visualization and dashboards +- Want to improve UX and design user-friendly interfaces +- Enjoy working with component libraries and styling systems + +**Key directories**: +- `src/components/` - Reusable UI components +- `src/pages/` - Route-level page components +- `src/services/` - API client and data fetching + +**Note**: The frontend currently uses mock data in some areas. Connecting it to the live backend API is ongoing work. + +--- + +### Engine + +**Location**: `/engine` + +**What it does**: The engine is the compliance brain of the platform. It contains data collectors that fetch configuration from cloud APIs, and Rego policies that evaluate whether that configuration meets compliance standards. + +**Tech stack**: +- Python for data collectors +- Open Policy Agent (OPA) for policy evaluation +- Rego policy language +- Microsoft Graph API client + +**Good fit if you**: +- Are interested in cloud security and compliance +- Want to learn policy-as-code with OPA and Rego +- Have experience with cloud platforms (Azure, M365, GCP) +- Enjoy working with APIs and data transformation + +**Key directories**: +- `collectors/` - Data collectors for cloud APIs (see `collectors/README.md`) +- `policies/` - Rego policies organized by framework (see `policies/README.md`) + +**Getting started**: The engine has detailed documentation for writing new collectors and policies. Start with the READMEs in the `collectors/` and `policies/` directories. + +--- + +### Infrastructure + +**Location**: `/infrastructure` + +**What it does**: Contains monitoring configuration, deployment infrastructure, and operational tooling for the platform. + +**NB:** AutoAudit currently doesn't have any configured infrastructure to deploy to, so the deployment part of this is conceptual at present. As a result, the monitoring and alerting is also conceptual. + +**Tech stack**: +- Docker and Docker Compose +- Monitoring tools +- CI/CD pipelines (GitHub Actions in `/.github/workflows`) + +**Good fit if you**: +- Have DevOps or platform engineering experience +- Are interested in monitoring, alerting, and observability +- Want to work on deployment automation and CI/CD +- Enjoy containerization and infrastructure-as-code + +**Key areas**: +- `monitoring/` - Monitoring and alerting configuration +- Root `docker-compose.yml` - Local development environment +- `/.github/workflows/` - CI/CD pipeline definitions + +--- + +## Git Workflow + +We use a simple branch-based workflow: + +1. **Create a feature branch from main** + ```bash + git checkout main + git pull origin main + git checkout -b feature/your-feature-name + ``` + +2. **Make your changes and commit** + ```bash + git add . + git commit -m "Add description of your changes" + ``` + +3. **Push your branch** + ```bash + git push origin feature/your-feature-name + ``` + +4. **Open a Pull Request** against the `main` branch on GitHub + +### Branch naming + +Use descriptive branch names that indicate what you're working on: +- `feature/add-user-roles` - New functionality +- `hotfix/login-redirect-bug` - Bug fixes +- `docs/update-readme` - Documentation changes +- `refactor/cleanup-api-routes` - Code improvements + +## Pull Request Guidelines + +Good pull requests make the review process smoother for everyone: + +- **Keep them focused** - One PR should do one thing. If you find yourself writing "and" in the description, consider splitting it up. + +- **Write a clear description** - Explain what the change does and why. Include screenshots for UI changes. + +- **Test your changes** - Run tests locally before pushing. If you're adding new functionality, add tests for it. + +- **Update documentation** - If your change affects how something works, update the relevant docs. + +- **Respond to feedback** - Reviewers may request changes. Address them or explain why you disagree. + +## Where to Get Help + +- **Microsoft Planner items** - Check what's on the board, add tasks for things you're working on and keep them updated so the team is aware +- **Code Review** - Ask questions in your PR if you're unsure about something +- **Team Leads** - Reach out to the relevant module or team lead for guidance + diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md new file mode 100644 index 00000000..e6ea3346 --- /dev/null +++ b/docs/GETTING_STARTED.md @@ -0,0 +1,178 @@ +# Getting Started + +This guide will help you get the AutoAudit development environment running on your machine. By the end, you'll have the full stack running locally and be ready to start contributing. + +## Prerequisites + +Before you begin, make sure you have the following installed: + +- **Git** - For version control +- **Docker Desktop** - For running the development services +- **VS Code** (recommended) - Or your preferred editor + +Depending on which module you'll be working on, you may also need: + +- **Python 3.10+** and **uv** - For backend-api or engine work +- **Node.js 20+** and **npm** - For frontend work + +## Clone the Repository + +```bash +git clone https://github.com/Hardhat-Enterprises/AutoAudit.git +cd AutoAudit +``` + +## Quick Start with Docker + +The fastest way to get everything running is with Docker Compose. This will start the full stack including the database, Redis, OPA, and all application services. + +```bash +docker compose --profile all up -d +``` + +Once the containers are running: + +- Frontend: http://localhost:3000 +- Backend API: http://localhost:8000 +- API Documentation: http://localhost:8000/docs +- OPA: http://localhost:8181 + +The backend automatically runs database migrations and seeds a default admin user on first startup: + +- Email: `admin@example.com` +- Password: `admin` + +## Development Profiles + +Docker Compose is configured with profiles to support different development workflows. This lets you run some services in Docker while developing others locally. + +### Infrastructure Only (default) + +Starts the infrastructure services. Use this when you want to run both the frontend and backend locally. + +```bash +docker compose up -d +``` + +Services started: +- PostgreSQL on port 5432 +- Redis on port 6379 +- OPA on port 8181 + +### Frontend Development + +If you're working on the frontend and want the backend running in Docker: + +```bash +docker compose --profile frontend-dev up -d +cd frontend +npm install +npm start +``` + +This starts the backend-api in Docker (port 8000), and you run the frontend locally (port 3000). + +### Backend Development + +If you're working on the backend and want the frontend running in Docker: + +```bash +docker compose --profile backend-dev up -d +cd backend-api +uv sync +uv run uvicorn app.main:app --reload --port 8000 +``` + +This starts the frontend in Docker (port 3000), and you run the backend locally (port 8000). + +### Full Stack in Docker + +For testing or demos, run everything in containers: + +```bash +docker compose --profile all up -d +``` + +## Module-Specific Setup + +If you're developing a specific module locally, here's how to set each one up. + +### Backend API + +The backend is a FastAPI application using Python and the uv package manager. + +```bash +cd backend-api + +# Install dependencies +uv sync + +# Start the development server with hot reload +uv run uvicorn app.main:app --reload --port 8000 +``` + +The API documentation will be available at http://localhost:8000/docs. + +Make sure PostgreSQL and Redis are running (via `docker compose up -d`) before starting the backend. + +### Frontend + +The frontend is a React application. + +```bash +cd frontend + +# Install dependencies +npm install + +# Start the development server +npm start +``` + +The app will open at http://localhost:3000. + +### Engine + +The engine handles compliance scanning and policy evaluation. It connects to OPA for policy decisions. + +```bash +cd engine + +# Create a virtual environment +python -m venv .venv + +# Activate it +# On Windows: +.venv\Scripts\activate +# On macOS/Linux: +source .venv/bin/activate + +# Install dependencies +pip install -r requirements.txt +``` + +The engine runs as a Celery worker in production. For local development, make sure OPA is running (`docker compose up -d`) and check the engine documentation for running specific collectors or policies. + +## Verifying Your Setup + +Here's how to confirm everything is working: + +1. **Database**: Connect to PostgreSQL at `localhost:5432` (user: `autoaudit`, password: `autoaudit_dev_password`, database: `autoaudit`) + +2. **Backend API**: Visit http://localhost:8000/docs - you should see the Swagger UI + +3. **Frontend**: Visit http://localhost:3000 - you should see the dashboard + +4. **OPA**: Visit http://localhost:8181/health - you should get a healthy response + +## Common Issues + +**Port already in use**: Check if you have other services running on ports 3000, 5432, 6379, 8000, or 8181. Stop them or modify the ports in docker-compose.yml. + +**Docker containers won't start**: Run `docker compose logs` to see error messages. Often it's a port conflict or insufficient resources allocated to Docker. + +**Database connection errors**: Make sure the db container is healthy before starting the backend. Check with `docker compose ps`. + +## Next Steps + +Now that you're set up, head over to [CONTRIBUTING.md](./CONTRIBUTING.md) to learn about the different modules and find the right area to start contributing based on your skills and interests. From 57909c7a80043d7de972c0dc979583a5fe220513 Mon Sep 17 00:00:00 2001 From: David Hartley Date: Fri, 12 Dec 2025 11:45:15 +1100 Subject: [PATCH 028/111] Remove unnecessary documentation file --- docs/engine/potential-folder-structure.md | 144 ---------------------- 1 file changed, 144 deletions(-) delete mode 100644 docs/engine/potential-folder-structure.md diff --git a/docs/engine/potential-folder-structure.md b/docs/engine/potential-folder-structure.md deleted file mode 100644 index fdc4bec4..00000000 --- a/docs/engine/potential-folder-structure.md +++ /dev/null @@ -1,144 +0,0 @@ -Plan: Engine Folder Restructure - - Requirements - - - Scan GCP, Azure, and M365 environments - - Evaluate against CIS benchmarks (other benchmarks in future) - - Support multiple versions of each benchmark - - Platform-first organization with rules per-platform - - Keep /security separate (potential future consolidation) - - Proposed Structure - - /engine - ├── pyproject.toml # Package definition - ├── __init__.py - │ - ├── /core # Shared engine infrastructure - │ ├── __init__.py - │ ├── evaluator.py # Base evaluation logic (OPA integration) - │ ├── reporter.py # Report generation (JSON, PDF) - │ ├── risk_rating.py # Severity/risk calculations - │ └── models.py # Shared data models (ScanResult, Finding, etc.) - │ - ├── /gcp # Google Cloud Platform - │ ├── __init__.py - │ ├── collector.py # GCP API client (current GCPAccess.py) - │ ├── /benchmarks - │ │ └── /cis - │ │ ├── __init__.py - │ │ ├── /v3.0 - │ │ │ ├── rules.rego # Rego policy rules - │ │ │ ├── metadata.json # Benchmark metadata - │ │ │ └── mappings.py # Control mappings - │ │ └── /v2.0 - │ │ └── ... - │ └── /test-configs # Mock GCP data for testing - │ - ├── /azure # Microsoft Azure - │ ├── __init__.py - │ ├── collector.py # Azure API client - │ ├── /benchmarks - │ │ └── /cis - │ │ ├── __init__.py - │ │ ├── /v3.0 - │ │ │ ├── rules.json # JSON rule definitions - │ │ │ └── metadata.json - │ │ └── /v2.1 - │ │ └── ... - │ └── /test-configs - │ - ├── /m365 # Microsoft 365 - │ ├── __init__.py - │ ├── collector.py # M365/Graph API client - │ ├── /benchmarks - │ │ └── /cis - │ │ └── /v3.1 - │ │ ├── rules.json - │ │ └── metadata.json - │ └── /test-configs - │ - └── /shared # Cross-platform utilities - ├── __init__.py - ├── opa.py # OPA CLI wrapper - └── helpers.rego # Shared Rego helper functions - - Key Design Decisions - - 1. Platform-First Hierarchy - - /engine/{platform}/benchmarks/{benchmark}/{version}/ - - Easy to find all GCP-related code in one place - - Teams can work on platforms independently - - Adding a new platform = add a new folder - - 2. Versioned Benchmarks - - /engine/gcp/benchmarks/cis/v3.0/ - /engine/gcp/benchmarks/cis/v2.0/ - /engine/gcp/benchmarks/nist/v1.0/ # Future - - Each version is immutable (important for compliance audits) - - Can run scans against specific versions - - Easy to add new benchmarks (just add folder) - - 3. Consistent Platform Structure - - Each platform follows the same pattern: - - collector.py - API client for that platform - - /benchmarks/{name}/{version}/ - Rules organized by benchmark - - /test-configs/ - Mock data for testing - - 4. Shared Core - - /engine/core/ contains: - - Evaluator logic (OPA integration) - - Report generation - - Common models and utilities - - Usage Examples - - # Import pattern - from engine.gcp.collector import GCPCollector - from engine.gcp.benchmarks.cis.v3_0 import rules - from engine.core.evaluator import evaluate - - # Evaluate GCP against CIS v3.0 - collector = GCPCollector(credentials) - config = collector.collect() - results = evaluate(config, "gcp", "cis", "v3.0") - - Migration from Current Structure - - | Current Location | New Location | - |------------------------------|-----------------------------------| - | engine/engine/GCPAccess.py | engine/gcp/collector.py | - | engine/engine/aggregator.py | engine/core/evaluator.py | - | engine/engine/risk_rating.py | engine/core/risk_rating.py | - | engine/engine/json_to_pdf.py | engine/core/reporter.py | - | engine/engine/Helpers.rego | engine/shared/helpers.rego | - | engine/rules/*.rego | engine/gcp/benchmarks/cis/v3.0/ | - | engine/Rules-Azure/*.json | engine/azure/benchmarks/cis/v3.0/ | - | engine/test-configs/ | engine/gcp/test-configs/ | - - Future Extensibility - - Adding a new platform (e.g., AWS) - - /engine/aws/ - ├── __init__.py - ├── collector.py - └── /benchmarks/cis/v3.0/ - - Adding a new benchmark (e.g., NIST) - - /engine/gcp/benchmarks/nist/v1.0/ - ├── rules.rego - └── metadata.json - - Adding a new version - - /engine/gcp/benchmarks/cis/v4.0/ - ├── rules.rego - └── metadata.json - - No refactoring required for any of these additions. \ No newline at end of file From 8cbd5e027ea1188cb42476f4dba3947e6086fd53 Mon Sep 17 00:00:00 2001 From: Romil Bijarnia Date: Sat, 13 Dec 2025 10:04:35 +0530 Subject: [PATCH 029/111] ui fixes, added recent scans a dialog component in the evidence page. --- backend-api/app/api/v1/evidence.py | 6 + backend-api/app/db/init_db.py | 9 + frontend/public/AutoAudit.webp | Bin 0 -> 40002 bytes frontend/public/bg.webp | Bin 0 -> 150462 bytes frontend/src/App.js | 13 +- frontend/src/components/Sidebar.js | 121 +++++++----- frontend/src/pages/Auth/LoginPage.css | 57 ++---- frontend/src/pages/Auth/SignUpPage.js | 25 ++- .../src/pages/Auth/components/BrandPanel.js | 29 ++- .../src/pages/Auth/components/LoginHeader.js | 2 +- .../src/pages/Auth/components/SignInPanel.js | 11 -- frontend/src/pages/Contact/ContactPage.css | 2 - frontend/src/pages/Dashboard.js | 92 +++++---- frontend/src/pages/Evidence.css | 127 ++++++++++++ frontend/src/pages/Evidence.js | 186 +++++++++++++++--- frontend/src/pages/Landing/AboutUs.js | 5 +- frontend/src/pages/Landing/LandingPage.css | 68 ++++--- frontend/src/pages/Landing/LandingPage.js | 5 +- .../Landing/components/BenefitsSection.js | 13 +- .../Landing/components/FeaturesSection.js | 6 +- .../pages/Landing/components/HeroSection.js | 19 +- .../pages/Landing/components/LandingHeader.js | 5 +- frontend/src/pages/Landing/featuresData.js | 14 +- frontend/src/styles/global.css | 8 + 24 files changed, 590 insertions(+), 233 deletions(-) create mode 100644 frontend/public/AutoAudit.webp create mode 100644 frontend/public/bg.webp diff --git a/backend-api/app/api/v1/evidence.py b/backend-api/app/api/v1/evidence.py index c68e98a4..4ce254a7 100644 --- a/backend-api/app/api/v1/evidence.py +++ b/backend-api/app/api/v1/evidence.py @@ -42,6 +42,12 @@ async def scan_mem(): return evidence_ui.scan_mem_page() +@router.get("/scan-mem-log") +async def scan_mem_log(): + """Return recent scans as JSON for the frontend.""" + return evidence_ui.api_get_scan_mem_log() + + @router.get("/recent-scans", include_in_schema=False) async def recent_scans_redirect(): return RedirectResponse(url="/v1/evidence/scan-mem") diff --git a/backend-api/app/db/init_db.py b/backend-api/app/db/init_db.py index a10db965..851b0989 100644 --- a/backend-api/app/db/init_db.py +++ b/backend-api/app/db/init_db.py @@ -36,6 +36,15 @@ async def init_db(): is_verified=True, ) + test_user = User( + email="dev@example.com", + hashed_password=password_helper.hash("test"), + role=Role.USER.value, + is_active=True, + is_superuser=False, + is_verified=True, + ) + session.add(test_user) session.add(admin_user) await session.commit() diff --git a/frontend/public/AutoAudit.webp b/frontend/public/AutoAudit.webp new file mode 100644 index 0000000000000000000000000000000000000000..0e9f94957d77ce21aa18f5641e63a30f5f1d509e GIT binary patch literal 40002 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≻ToKOv9?sdieC&4YFm@j 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 C7{9v! literal 0 HcmV?d00001 diff --git a/frontend/public/bg.webp b/frontend/public/bg.webp new file mode 100644 index 0000000000000000000000000000000000000000..8ba4f2097bdb26648623d7e753ab82cca3a3eddd GIT binary patch literal 150462 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

aW`M>cm-?&N<;FqJ9GZ(**o9pl<486CvIR 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 F{{d{YW90w< literal 0 HcmV?d00001 diff --git a/frontend/src/App.js b/frontend/src/App.js index 33cbf4fc..01da14e6 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -55,7 +55,18 @@ function App() { const auth = useAuth(); // Dashboard state - const [sidebarWidth, setSidebarWidth] = useState(220); + const getInitialSidebarWidth = () => { + if (typeof window === "undefined") return 220; + try { + const stored = window.localStorage.getItem("sidebarExpanded"); + if (stored === null) return 220; + return stored === "true" ? 220 : 80; + } catch { + return 220; + } + }; + + const [sidebarWidth, setSidebarWidth] = useState(getInitialSidebarWidth); const [isDarkMode, setIsDarkMode] = useState(true); const location = useLocation(); diff --git a/frontend/src/components/Sidebar.js b/frontend/src/components/Sidebar.js index f43c5334..7cdbb1dc 100644 --- a/frontend/src/components/Sidebar.js +++ b/frontend/src/components/Sidebar.js @@ -4,7 +4,19 @@ import React, { useState } from "react"; import { useNavigate, useLocation } from "react-router-dom"; -import './Sidebar.css'; +import { + LayoutDashboard, + Cloud, + FileSearch, + ShieldCheck, + FileText, + Settings2, + User, + Menu, + ArrowLeft, + Search, +} from "lucide-react"; +import "./Sidebar.css"; //Button component that we use throughout the sidebar @@ -13,7 +25,7 @@ import './Sidebar.css'; //name - text to display in expanded view //icon - text to display in collapsed view -const NavButton = ({ href, name, icon, isExpanded, isActive = false, onClick }) => { +const NavButton = ({ href, name, icon: Icon, isExpanded, isActive = false, onClick }) => { const handleClick = (clickEvent) => { if (onClick) { clickEvent.preventDefault(); @@ -28,7 +40,9 @@ const NavButton = ({ href, name, icon, isExpanded, isActive = false, onClick }) href={href} onClick={handleClick} > - {icon} + {isExpanded && {name}} @@ -36,10 +50,21 @@ const NavButton = ({ href, name, icon, isExpanded, isActive = false, onClick }) }; // Main sidebar component +const SIDEBAR_EXPANDED_KEY = "sidebarExpanded"; + const Sidebar = ({ onWidthChange = () => {}, isDarkMode = true }) => { const navigate = useNavigate(); const location = useLocation(); - const [isExpanded, setIsExpanded] = useState(true); //Track whether sidebar is expanded + const [isExpanded, setIsExpanded] = useState(() => { + if (typeof window === "undefined") return true; + try { + const stored = window.localStorage.getItem(SIDEBAR_EXPANDED_KEY); + if (stored === null) return true; + return stored === "true"; + } catch { + return true; + } + }); // Track whether sidebar is expanded (persisted) const [searchValue, setSearchValue] = useState(''); // Track search input value // Determine active item based on current route @@ -62,6 +87,13 @@ const Sidebar = ({ onWidthChange = () => {}, isDarkMode = true }) => { const newExpanded = !isExpanded; setIsExpanded(newExpanded); onWidthChange(newExpanded ? 220 : 80); + if (typeof window !== "undefined") { + try { + window.localStorage.setItem(SIDEBAR_EXPANDED_KEY, String(newExpanded)); + } catch { + // ignore storage errors (private mode, blocked, etc.) + } + } }; //Navigate to the specified route @@ -82,7 +114,7 @@ const Sidebar = ({ onWidthChange = () => {}, isDarkMode = true }) => { {isExpanded ? (

{}, isDarkMode = true }) => { onChange={handleSearchChange} className="search-input" /> - 🔍 + + +
) : ( )}
@@ -103,64 +137,63 @@ const Sidebar = ({ onWidthChange = () => {}, isDarkMode = true }) => { {/* Main navigation area */}
    handleNavClick('home', '/dashboard')} + isActive={activeItem === "home"} + onClick={() => handleNavClick("home", "/dashboard")} /> handleNavClick('cloud-platforms', '/cloud-platforms')} + isActive={activeItem === "cloud-platforms"} + onClick={() => handleNavClick("cloud-platforms", "/cloud-platforms")} /> handleNavClick('scans', '/scans')} + isActive={activeItem === "scans"} + onClick={() => handleNavClick("scans", "/scans")} /> handleNavClick('tasks', '/evidence-scanner')} + isActive={activeItem === "tasks"} + onClick={() => handleNavClick("tasks", "/evidence-scanner")} /> - handleNavClick('reports', '/reports')} - /> + isActive={activeItem === "reports"} + /> */}
{/* Settings section at bottom */}
    handleNavClick('settings', '/settings')} + isActive={activeItem === "settings"} + onClick={() => handleNavClick("settings", "/settings")} /> handleNavClick('account', '/account')} + isActive={activeItem === "account"} + onClick={() => handleNavClick("account", "/account")} />
diff --git a/frontend/src/pages/Auth/LoginPage.css b/frontend/src/pages/Auth/LoginPage.css index 7cf47000..5fad2cf1 100644 --- a/frontend/src/pages/Auth/LoginPage.css +++ b/frontend/src/pages/Auth/LoginPage.css @@ -71,7 +71,12 @@ } .login-brand { - background: linear-gradient(135deg, #0a1628 0%, #162a4a 50%, #1e3a5f 100%); + background-image: none; + background-clip: unset; + -webkit-background-clip: unset; + color: rgba(255, 255, 255, 1); + background-color: unset; + background: unset; position: relative; padding: 2.5rem 3rem; display: flex; @@ -195,17 +200,6 @@ height: auto; margin: 0 auto 1rem; filter: drop-shadow(0 10px 25px rgba(64, 224, 208, 0.3)); - animation: floatLogo 4s ease-in-out infinite; -} - -@keyframes floatLogo { - 0%, - 100% { - transform: translateY(0); - } - 50% { - transform: translateY(-12px); - } } .brand-text h1 { @@ -374,7 +368,12 @@ padding: 1rem; border-radius: 12px; border: none; - background: linear-gradient(135deg, #40e0d0, #1e90ff); + background-color: rgba(255, 255, 255, 1); + background-image: linear-gradient( + 135deg, + rgba(64, 224, 208, 1) 0%, + rgba(30, 144, 255, 1) 100% + ); color: #ffffff; font-size: 1rem; font-weight: 600; @@ -475,38 +474,6 @@ color: #1e90ff; } -.security-info { - display: flex; - gap: 1rem; - align-items: center; - padding: 1rem; - border-radius: 12px; - border: 1px solid rgba(64, 224, 208, 0.2); - background: rgba(64, 224, 208, 0.05); - margin-top: 1.5rem; -} - -.security-icon-box { - width: 44px; - height: 44px; - border-radius: 12px; - background: linear-gradient(135deg, #40e0d0, #1e90ff); - display: flex; - align-items: center; - justify-content: center; -} - -.security-info-text h4 { - margin: 0; - font-size: 0.95rem; -} - -.security-info-text p { - margin: 0; - color: #b0c4de; - font-size: 0.85rem; -} - @media (max-width: 1024px) { .login-main { grid-template-columns: 1fr; diff --git a/frontend/src/pages/Auth/SignUpPage.js b/frontend/src/pages/Auth/SignUpPage.js index 9833d887..c728ddce 100644 --- a/frontend/src/pages/Auth/SignUpPage.js +++ b/frontend/src/pages/Auth/SignUpPage.js @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { Eye, EyeOff, ShieldCheck } from "lucide-react"; import './SignUpPage.css'; export default function SignUpPage({ onSignUp, onBackToLogin }) { @@ -44,14 +45,20 @@ export default function SignUpPage({ onSignUp, onBackToLogin }) {
- Signup bg + + + Signup bg +
- AutoAudit Logo + + + AutoAudit Logo +

AutoAudit

Microsoft 365 Compliance Platform

@@ -123,8 +130,9 @@ export default function SignUpPage({ onSignUp, onBackToLogin }) { type="button" onClick={() => setShowPassword(!showPassword)} className="password-toggle" + aria-label={showPassword ? "Hide password" : "Show password"} > - {showPassword ? '👁️' : '👁️‍🗨️'} + {showPassword ? : }
@@ -143,8 +151,9 @@ export default function SignUpPage({ onSignUp, onBackToLogin }) { type="button" onClick={() => setShowConfirmPassword(!showConfirmPassword)} className="password-toggle" + aria-label={showConfirmPassword ? "Hide password" : "Show password"} > - {showConfirmPassword ? '👁️' : '👁️‍🗨️'} + {showConfirmPassword ? : } @@ -161,7 +170,7 @@ export default function SignUpPage({ onSignUp, onBackToLogin }) {
@@ -174,7 +183,9 @@ export default function SignUpPage({ onSignUp, onBackToLogin }) {
- 🛡️ +

Enterprise Security Standards

Your data is protected with enterprise-grade encryption and follows strict compliance protocols

@@ -186,4 +197,4 @@ export default function SignUpPage({ onSignUp, onBackToLogin }) {
); -} \ No newline at end of file +} diff --git a/frontend/src/pages/Auth/components/BrandPanel.js b/frontend/src/pages/Auth/components/BrandPanel.js index 6ebcfc34..8db0f421 100644 --- a/frontend/src/pages/Auth/components/BrandPanel.js +++ b/frontend/src/pages/Auth/components/BrandPanel.js @@ -1,9 +1,10 @@ import React from "react"; +import { Lock, Zap, BarChart3 } from "lucide-react"; const brandFeatures = [ - { icon: "🔒", text: "Enterprise-grade security & encryption" }, - { icon: "⚡", text: "Real-time compliance monitoring" }, - { icon: "📊", text: "Actionable reporting & insights" }, + { icon: Lock, text: "Enterprise-grade security & encryption" }, + { icon: Zap, text: "Real-time compliance monitoring" }, + { icon: BarChart3, text: "Actionable reporting & insights" }, ]; const BrandPanel = () => { @@ -17,7 +18,10 @@ const BrandPanel = () => { ))}
- AutoAudit + + + AutoAudit +

Access security insights anywhere

@@ -28,12 +32,17 @@ const BrandPanel = () => {
- {brandFeatures.map((feature) => ( -
- {feature.icon} - {feature.text} -
- ))} + {brandFeatures.map((feature) => { + const Icon = feature.icon; + return ( +
+ + {feature.text} +
+ ); + })}
diff --git a/frontend/src/pages/Auth/components/LoginHeader.js b/frontend/src/pages/Auth/components/LoginHeader.js index c6b8a4a2..acdd21e8 100644 --- a/frontend/src/pages/Auth/components/LoginHeader.js +++ b/frontend/src/pages/Auth/components/LoginHeader.js @@ -12,7 +12,7 @@ const LoginHeader = () => { return (
- AutoAudit + AutoAudit
); diff --git a/frontend/src/pages/Contact/ContactPage.css b/frontend/src/pages/Contact/ContactPage.css index f53fcb2b..cbc0805d 100644 --- a/frontend/src/pages/Contact/ContactPage.css +++ b/frontend/src/pages/Contact/ContactPage.css @@ -108,7 +108,6 @@ .info-card:hover { border-color: #40e0d0; background: rgba(255, 255, 255, 0.05); - transform: translateY(-5px); } .info-icon { @@ -169,7 +168,6 @@ .social-link:hover { background: linear-gradient(135deg, #40e0d0, #1e90ff); border-color: transparent; - transform: translateY(-3px); } .social-link svg { diff --git a/frontend/src/pages/Dashboard.js b/frontend/src/pages/Dashboard.js index 44583cca..8e60451c 100644 --- a/frontend/src/pages/Dashboard.js +++ b/frontend/src/pages/Dashboard.js @@ -1,18 +1,27 @@ -import React, { useState } from 'react'; -import './Dashboard.css'; -import ComplianceChart from '../components/ComplianceChart'; -import Dropdown from '../components/Dropdown'; +import React, { useState } from "react"; +import "./Dashboard.css"; +import ComplianceChart from "../components/ComplianceChart"; +import Dropdown from "../components/Dropdown"; import { useNavigate } from "react-router-dom"; +import { + CheckCircle2, + AlertTriangle, + Clock3, + Shield, + AlertOctagon, + Sun, + Moon, +} from "lucide-react"; export default function Dashboard({ sidebarWidth = 220, isDarkMode, onThemeToggle }) { const navigate = useNavigate(); const stats = [ - { label: 'Compliance Score', value: '85%', className: 'emerald', subtitle: 'Overall security posture' }, - { label: 'Failed Checks', value: '12', className: 'orange', subtitle: 'Requiring immediate attention' }, - { label: 'Last Scan', value: '2h ago', className: 'gray', subtitle: 'Monday, August 14, 2025' }, - { label: 'Total Controls', value: '97', className: 'gray', subtitle: 'CIS Rules Benchmark' } + { label: "Compliance Score", value: "85%", className: "emerald", subtitle: "Overall security posture", icon: CheckCircle2 }, + { label: "Failed Checks", value: "12", className: "orange", subtitle: "Requiring immediate attention", icon: AlertTriangle }, + { label: "Last Scan", value: "2h ago", className: "gray", subtitle: "Monday, August 14, 2025", icon: Clock3 }, + { label: "Total Controls", value: "97", className: "gray", subtitle: "CIS Rules Benchmark", icon: Shield } ]; const benchmarkOptions = [ @@ -45,18 +54,24 @@ export default function Dashboard({ sidebarWidth = 220, isDarkMode, onThemeToggl return (
- AutoAudit Logo + + + AutoAudit Logo +

AutoAudit

@@ -64,8 +79,8 @@ export default function Dashboard({ sidebarWidth = 220, isDarkMode, onThemeToggl
-
- 🌞 +
+ - 🌙 +
@@ -104,24 +119,25 @@ export default function Dashboard({ sidebarWidth = 220, isDarkMode, onThemeToggl
- {stats.map((stat, index) => ( -
-
-
-
- {stat.className === 'emerald' && } - {stat.className === 'orange' && } - {stat.className === 'gray' && 🕐} -
-
-

{stat.label}

-

{stat.value}

-

{stat.subtitle}

+ {stats.map((stat, index) => { + const Icon = stat.icon; // uppercase component so React renders the imported icon + return ( +
+
+
+ +
+

{stat.label}

+

{stat.value}

+

{stat.subtitle}

+
-
- ))} + ); + })}
@@ -144,7 +160,9 @@ export default function Dashboard({ sidebarWidth = 220, isDarkMode, onThemeToggl
- ! +

High Priority Issues

3 @@ -155,7 +173,9 @@ export default function Dashboard({ sidebarWidth = 220, isDarkMode, onThemeToggl
- +

Medium Priority Issues

9 @@ -166,7 +186,9 @@ export default function Dashboard({ sidebarWidth = 220, isDarkMode, onThemeToggl
- +

Scan Status

Complete diff --git a/frontend/src/pages/Evidence.css b/frontend/src/pages/Evidence.css index 818df3b5..95cb5fbb 100644 --- a/frontend/src/pages/Evidence.css +++ b/frontend/src/pages/Evidence.css @@ -211,6 +211,8 @@ -webkit-appearance: none; -moz-appearance: none; appearance: none; + /* Match other app dropdowns in dark mode */ + background: var(--bg-tertiary, #334155); background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%2394a3b8' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; @@ -220,6 +222,11 @@ line-height: 1.4; } +.form-select option { + background: var(--bg-secondary, #1e293b); + color: var(--text-primary); +} + .form-select:focus, .form-input:focus, .form-file:focus { @@ -500,6 +507,126 @@ color: var(--text-primary); } +/* Recent scans dialog */ +.recent-scans-dialog { + border: none; + padding: 0; + background: transparent; + width: min(960px, calc(100vw - 48px)); + max-width: 960px; +} + +.recent-scans-dialog::backdrop { + background: rgba(0, 0, 0, 0.6); +} + +.recent-scans-dialog__surface { + background: var(--bg-secondary, #1e293b); + color: var(--text-primary); + border: 1px solid var(--border-color, #334155); + border-radius: 16px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45); + overflow: hidden; +} + +.recent-scans-dialog__header { + padding: 18px 20px 0; +} + +.recent-scans-dialog__body { + padding: 14px 20px 18px; + max-height: min(70vh, 560px); + overflow: auto; +} + +.recent-scans-info { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 12px 14px; + border-radius: 12px; + border: 1px solid rgba(100, 223, 223, 0.25); + background: rgba(100, 223, 223, 0.08); + color: var(--text-primary); + margin-bottom: 14px; +} + +.recent-scans-info__text { + margin: 0; + line-height: 1.45; + color: var(--text-secondary); + font-size: 13px; +} + +.recent-scans-info__link { + color: var(--teal); + text-decoration: none; + font-weight: 600; +} + +.recent-scans-info__link:hover { + color: #22D3EE; + text-decoration: underline; +} + +.recent-scans-dialog__footer { + padding: 12px 20px 18px; + display: flex; + justify-content: flex-end; + gap: 12px; + border-top: 1px solid var(--border-color, #334155); + background: var(--bg-secondary, #1e293b); +} + +.recent-scans-trigger:hover:not(:disabled), +.recent-scans-trigger:focus-visible { + border-color: var(--teal); + box-shadow: 0 0 0 2px rgba(100, 223, 223, 0.18); + background: var(--border-color, #475569); +} + +.recent-scans-table { + width: 100%; + border-collapse: collapse; + margin-top: 8px; + font-size: 14px; +} + +.recent-scans-table th, +.recent-scans-table td { + border: 1px solid var(--border-color, #334155); + padding: 12px; + text-align: left; + vertical-align: top; +} + +.recent-scans-table th { + background: var(--bg-tertiary, #334155); + color: var(--text-primary); + font-weight: 600; + font-size: 13px; +} + +.recent-scans-table td { + background: var(--bg-secondary, #1e293b); + color: var(--text-primary); +} + +.scan-status-success { + color: var(--success); + font-weight: 600; +} + +.scan-status-fail { + color: var(--error); + font-weight: 600; +} + +.scan-status-muted { + color: var(--text-secondary); + font-weight: 600; +} + /* Status Colors */ .status-pass { color: var(--success); diff --git a/frontend/src/pages/Evidence.js b/frontend/src/pages/Evidence.js index 84aae3bb..f54cda80 100644 --- a/frontend/src/pages/Evidence.js +++ b/frontend/src/pages/Evidence.js @@ -1,8 +1,11 @@ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState, useEffect, useMemo, useRef } from 'react'; +import { Info, X } from 'lucide-react'; import './Evidence.css'; -import { useNavigate } from 'react-router-dom'; import { parseApiError, formatEvidenceList } from '../utils/api'; +const RECENT_SCANS_INFO_MESSAGE = + 'This table shows the 5 most recent scans recorded by the Evidence service. If you need additional history or assistance, please contact support@autoaudit.'; + const EvidenceDetails = ({ details, evidence }) => { const toText = (value) => { if (!value) return ''; @@ -89,15 +92,33 @@ const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => { const [health, setHealth] = useState(null); const [selectedStrategy, setSelectedStrategy] = useState(''); - const [userId, setUserId] = useState(''); const [selectedFile, setSelectedFile] = useState(null); const [isScanning, setIsScanning] = useState(false); const [error, setError] = useState(''); const [errorDetails, setErrorDetails] = useState([]); const [note, setNote] = useState(''); const [results, setResults] = useState(null); + const [recentScansOpen, setRecentScansOpen] = useState(false); + const [recentScans, setRecentScans] = useState([]); + const [recentScansLoading, setRecentScansLoading] = useState(false); + const [recentScansError, setRecentScansError] = useState(''); + const recentScansDialogRef = useRef(null); + + useEffect(() => { + const dialog = recentScansDialogRef.current; + if (!dialog) return; + + if (recentScansOpen) { + if (!dialog.open && typeof dialog.showModal === 'function') { + dialog.showModal(); + } + return; + } - const navigate = useNavigate(); + if (dialog.open) { + dialog.close(); + } + }, [recentScansOpen]); useEffect(() => { let isActive = true; @@ -202,7 +223,8 @@ const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => { // Send strategy under both names to satisfy legacy and current API handlers formData.append('strategy_name', selectedStrategy); formData.append('strategy', selectedStrategy); - formData.append('user_id', userId || 'user'); + // User ID input removed for now; send a stable default. + formData.append('user_id', 'user'); formData.append('evidence', selectedFile); try { @@ -249,40 +271,77 @@ const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => { } }; + const getRecentScanStatusClass = (status) => { + const normalized = String(status || '').toLowerCase(); + if (normalized === 'success' || normalized === 'ok' || normalized === 'pass') { + return 'scan-status-success'; + } + if (normalized === 'fail' || normalized === 'error' || normalized === 'bad') { + return 'scan-status-fail'; + } + return 'scan-status-muted'; + }; + const selectedStrategyData = useMemo( () => strategies.find((s) => s.name === selectedStrategy), [strategies, selectedStrategy] ); - const openRecentScans = () => { + const fetchRecentScans = async () => { const base = apiBase || apiCandidates[0]; - if (base) { - window.open(`${base}/scan-mem`, '_blank', 'noopener'); + if (!base) { + setRecentScansError('Evidence API is not configured.'); + return; + } + setRecentScansLoading(true); + setRecentScansError(''); + try { + const res = await fetch(`${base}/scan-mem-log`); + if (!res.ok) { + throw new Error(`Failed to load recent scans (${res.status})`); + } + const data = await res.json(); + setRecentScans(Array.isArray(data) ? data : []); + } catch (err) { + setRecentScansError(err?.message || 'Unable to load recent scans.'); + setRecentScans([]); + } finally { + setRecentScansLoading(false); } }; + const toggleRecentScans = () => { + const nextOpen = !recentScansOpen; + setRecentScansOpen(nextOpen); + if (nextOpen && recentScans.length === 0 && !recentScansLoading) { + fetchRecentScans(); + } + }; + + const closeRecentScansDialog = () => { + setRecentScansOpen(false); + }; + const buildReportLink = (filename) => { const base = apiBase || apiCandidates[0] || ''; return base ? `${base}/reports/${encodeURIComponent(filename)}` : '#'; }; + const recentScansTop = useMemo(() => { + if (!Array.isArray(recentScans)) return []; + return recentScans.slice(0, 5); + }, [recentScans]); + return (
- {/* Back Button - Fixed to go to Dashboard */} -
- navigate("/dashboard")}> - ← Back - -
- {/* Brand Header */}
@@ -330,18 +389,6 @@ const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => {
No strategies returned by the API.
)}
- -
- - setUserId(e.target.value)} - /> -
@@ -379,9 +426,10 @@ const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => { @@ -403,6 +451,84 @@ const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => {
{note}
)} + setRecentScansOpen(false)} + onCancel={(e) => { + e.preventDefault(); + closeRecentScansDialog(); + }} + > +
+
+

Recent Scans

+
+ +
+
+
+ + {recentScansLoading && ( +
Loading recent scans…
+ )} + + {!recentScansLoading && recentScansError && ( +
{recentScansError}
+ )} + + {!recentScansLoading && !recentScansError && ( + recentScansTop.length > 0 ? ( + + + + + + + + + + + {recentScansTop.map((scan, idx) => ( + + + + + + + ))} + +
TimestampUser NameStrategyStatus
{scan.ts || scan.time || ''}{scan.user || scan.user_id || 'user'}{scan.strategy || scan.strategy_name || ''} + {scan.status || ''} +
+ ) : ( +
No runs yet.
+ ) + )} +
+ +
+ +
+
+
+ {/* Results Section */} {results && (
diff --git a/frontend/src/pages/Landing/AboutUs.js b/frontend/src/pages/Landing/AboutUs.js index dbdc8db3..0787fac8 100644 --- a/frontend/src/pages/Landing/AboutUs.js +++ b/frontend/src/pages/Landing/AboutUs.js @@ -60,7 +60,10 @@ const AboutUs = ({ onSignInClick = () => {} }) => {
- AutoAudit + + + AutoAudit +

About AutoAudit

Revolutionizing Cloud Compliance for Modern Enterprises

diff --git a/frontend/src/pages/Landing/LandingPage.css b/frontend/src/pages/Landing/LandingPage.css index ee571c8c..5ec976be 100644 --- a/frontend/src/pages/Landing/LandingPage.css +++ b/frontend/src/pages/Landing/LandingPage.css @@ -122,6 +122,19 @@ transform: translateY(-2px); } +/* LandingHeader overrides */ +.landing-header .btn-primary { + flex-wrap: wrap; + white-space: normal; + border-radius: 100px; + border-top-left-radius: 100px; + border-top-right-radius: 100px; + border-bottom-right-radius: 100px; + border-bottom-left-radius: 100px; + background-color: rgba(48, 128, 141, 1); + background-image: none; +} + /* Hero */ .landing-hero { background: var(--bg-gradient); @@ -182,9 +195,15 @@ font-size: clamp(2.5rem, 5vw, 3.5rem); margin-bottom: 1.5rem; line-height: 1.2; - background: linear-gradient(135deg, #ffffff, var(--accent)); + background-image: linear-gradient( + 135deg, + rgba(255, 255, 255, 1) 0%, + rgba(64, 224, 208, 1) 100% + ); -webkit-background-clip: text; + background-clip: text; -webkit-text-fill-color: transparent; + color: transparent; } .hero-text p { @@ -194,12 +213,25 @@ margin-bottom: 2rem; } +.section-tag { + color: rgba(255, 255, 255, 1); +} + .hero-buttons { display: flex; flex-wrap: wrap; gap: 1rem; } +.landing-hero .btn-primary { + background: linear-gradient( + 135deg, + rgba(64, 224, 208, 1) 0%, + rgba(30, 144, 255, 1) 0% + ); + justify-content: flex-start; +} + .hero-visual { display: flex; flex-direction: column; @@ -212,32 +244,17 @@ border-radius: 20px; padding: 1.75rem; backdrop-filter: blur(10px); - animation: float 6s ease-in-out infinite; -} - -.floating-card:nth-child(2) { - animation-delay: 0.8s; -} - -.floating-card:nth-child(3) { - animation-delay: 1.6s; -} - -@keyframes float { - 0%, - 100% { - transform: translateY(0); - } - 50% { - transform: translateY(-18px); - } } .card-icon { width: 48px; height: 48px; border-radius: 14px; - background: linear-gradient(135deg, var(--accent), var(--accent-strong)); + background: linear-gradient( + 135deg, + rgba(64, 224, 208, 1) 0%, + rgba(30, 144, 255, 1) 0% + ); display: inline-flex; align-items: center; justify-content: center; @@ -259,6 +276,7 @@ .landing-stats { background: linear-gradient(135deg, #0a1628, #1e3a5f); padding: 5rem 5%; + text-align: left; } .stats-grid { @@ -270,7 +288,7 @@ } .stat-card { - text-align: center; + text-align: left; padding: 2rem; border-radius: 20px; border: 1px solid var(--border); @@ -289,12 +307,18 @@ margin-bottom: 0.5rem; background: linear-gradient(135deg, var(--accent), var(--accent-strong)); -webkit-background-clip: text; + background-clip: text; -webkit-text-fill-color: transparent; + text-align: left; + width: 167px; + display: flex; + flex-wrap: wrap; } .stat-label { color: var(--text-muted); font-size: 1.1rem; + text-align: left; } /* Features */ diff --git a/frontend/src/pages/Landing/LandingPage.js b/frontend/src/pages/Landing/LandingPage.js index cd866a7e..98c160ac 100644 --- a/frontend/src/pages/Landing/LandingPage.js +++ b/frontend/src/pages/Landing/LandingPage.js @@ -11,9 +11,10 @@ import LandingFooter from "./components/LandingFooter"; const LandingPage = ({ onSignInClick }) => { return (
+ {/* this page should not do an entire call to a component here, we dont need a sign in button at this page - todo */} + onSignInClick={onSignInClick} + />
diff --git a/frontend/src/pages/Landing/components/BenefitsSection.js b/frontend/src/pages/Landing/components/BenefitsSection.js index a684d08e..a37f64e7 100644 --- a/frontend/src/pages/Landing/components/BenefitsSection.js +++ b/frontend/src/pages/Landing/components/BenefitsSection.js @@ -1,26 +1,27 @@ import React from "react"; +import { Timer, Target, ShieldCheck, Lightbulb } from "lucide-react"; const benefits = [ { - icon: "⏱️", + icon: Timer, title: "Save Time & Resources", description: "Reduce manual compliance work by 80% so your team can focus on strategic initiatives.", }, { - icon: "🎯", + icon: Target, title: "Stay Ahead of Threats", description: "Proactive monitoring identifies vulnerabilities before they are exploited.", }, { - icon: "✅", + icon: ShieldCheck, title: "Ensure Compliance", description: "Stay aligned with CIS, NIST, ISO 27001, SOC 2, and other regulatory frameworks.", }, { - icon: "💡", + icon: Lightbulb, title: "Expert Guidance", description: "Every finding includes prioritized remediation steps to improve security posture.", @@ -48,7 +49,9 @@ const BenefitsSection = () => {

Why Choose AutoAudit?

{benefits.map((benefit) => (
-
{benefit.icon}
+
+ +

{benefit.title}

{benefit.description}

diff --git a/frontend/src/pages/Landing/components/FeaturesSection.js b/frontend/src/pages/Landing/components/FeaturesSection.js index ca639100..5e7bf41c 100644 --- a/frontend/src/pages/Landing/components/FeaturesSection.js +++ b/frontend/src/pages/Landing/components/FeaturesSection.js @@ -1,9 +1,11 @@ import React from "react"; import { landingFeatures } from "../featuresData"; -const FeatureCard = ({ icon, title, description }) => ( +const FeatureCard = ({ icon: Icon, title, description }) => (
-
{icon}
+
+ +

{title}

{description}

diff --git a/frontend/src/pages/Landing/components/HeroSection.js b/frontend/src/pages/Landing/components/HeroSection.js index c350d165..1cfc2e94 100644 --- a/frontend/src/pages/Landing/components/HeroSection.js +++ b/frontend/src/pages/Landing/components/HeroSection.js @@ -1,18 +1,19 @@ import React from "react"; +import { Lock, Bolt, BarChart3 } from "lucide-react"; const floatingCards = [ { - icon: "🔒", + icon: Lock, title: "99.9% Uptime", subtitle: "Enterprise-grade reliability you can trust", }, { - icon: "⚡", + icon: Bolt, title: "Real-Time Monitoring", subtitle: "Instant alerts and comprehensive insights", }, { - icon: "📊", + icon: BarChart3, title: "Actionable Reports", subtitle: "Export audit-ready documentation instantly", }, @@ -41,11 +42,13 @@ const HeroSection = ({ onSignInClick }) => {
diff --git a/frontend/src/pages/Landing/components/LandingHeader.js b/frontend/src/pages/Landing/components/LandingHeader.js index e29d07fa..360e8e02 100644 --- a/frontend/src/pages/Landing/components/LandingHeader.js +++ b/frontend/src/pages/Landing/components/LandingHeader.js @@ -12,7 +12,10 @@ const LandingHeader = ({ onSignInClick, hiddenLinks = [], showSignIn = true }) = return (
- AutoAudit + + + AutoAudit +
-
); } diff --git a/frontend/src/pages/Auth/components/SignInPanel.js b/frontend/src/pages/Auth/components/SignInPanel.js index f7172279..4df15ac1 100644 --- a/frontend/src/pages/Auth/components/SignInPanel.js +++ b/frontend/src/pages/Auth/components/SignInPanel.js @@ -10,6 +10,43 @@ import { } from "lucide-react"; import { useAuth } from "../../../context/AuthContext"; +const socialButtons = [ + { + label: "Google", + icon: ( + + ), + }, + { + label: "Microsoft", + icon: ( + + ), + }, +]; + const SignInPanel = ({ onLogin, onSignUpClick }) => { const auth = useAuth(); const [formData, setFormData] = useState({ @@ -149,14 +186,16 @@ const SignInPanel = ({ onLogin, onSignUpClick }) => {
- or continue with + Or sign in with
-
- +
+ {socialButtons.map((button) => ( + + ))}

diff --git a/frontend/src/pages/Auth/components/SignupBrandPanel.js b/frontend/src/pages/Auth/components/SignupBrandPanel.js index b943e5b8..2269c8fa 100644 --- a/frontend/src/pages/Auth/components/SignupBrandPanel.js +++ b/frontend/src/pages/Auth/components/SignupBrandPanel.js @@ -1,9 +1,10 @@ import React from "react"; +import { BarChart3, Lock, Zap } from "lucide-react"; const featureItems = [ - { icon: "⚡", text: "Setup in minutes, not hours" }, - { icon: "🔒", text: "Bank-level security & encryption" }, - { icon: "📊", text: "Real-time compliance monitoring" }, + { icon: Zap, text: "Setup in minutes, not hours" }, + { icon: Lock, text: "Bank-level security & encryption" }, + { icon: BarChart3, text: "Real-time compliance monitoring" }, ]; const SignupBrandPanel = () => { @@ -33,14 +34,17 @@ const SignupBrandPanel = () => {

- {featureItems.map((item) => ( -
- - {item.text} -
- ))} + {featureItems.map((item) => { + const Icon = item.icon; + return ( +
+ + {item.text} +
+ ); + })}
diff --git a/frontend/src/pages/Auth/components/SignupFormPanel.js b/frontend/src/pages/Auth/components/SignupFormPanel.js index 0cc40278..c41f2367 100644 --- a/frontend/src/pages/Auth/components/SignupFormPanel.js +++ b/frontend/src/pages/Auth/components/SignupFormPanel.js @@ -1,5 +1,8 @@ import React, { useState } from "react"; -import { Eye, EyeOff, Mail, Building, User, ShieldCheck } from "lucide-react"; +import { ArrowRight, Eye, EyeOff, Mail, Building, User, ShieldCheck } from "lucide-react"; + +const TERMS_ERROR_MESSAGE = "Please agree to the terms and privacy policy"; +const PASSWORD_MISMATCH_MESSAGE = "These passwords do not match"; const inputFields = [ { @@ -36,27 +39,55 @@ const socialButtons = [ { label: "Google", icon: ( - I agree to the Terms & Conditions and Privacy Policy - {error && ( + {(error || submitError) && (

- {error} + {error || submitError}

)} @@ -221,16 +252,6 @@ const SignupFormPanel = ({ formData, onFormChange, onSubmit, onBackToLogin }) =>

Already have an account?

- -
- -
-

Enterprise Security Standards

-

Your data is protected with enterprise-grade encryption and strict compliance controls.

-
-
); diff --git a/frontend/src/pages/Connections/ConnectionsPage.css b/frontend/src/pages/Connections/ConnectionsPage.css index 67b73ed6..5d51788e 100644 --- a/frontend/src/pages/Connections/ConnectionsPage.css +++ b/frontend/src/pages/Connections/ConnectionsPage.css @@ -110,6 +110,11 @@ transition: border-color 0.2s ease; } +.form-group select option { + background: var(--bg-secondary); + color: var(--text-primary); +} + .form-group input:focus, .form-group select:focus { outline: none; diff --git a/frontend/src/pages/Landing/LandingPage.css b/frontend/src/pages/Landing/LandingPage.css index 496c40fc..87dfd48f 100644 --- a/frontend/src/pages/Landing/LandingPage.css +++ b/frontend/src/pages/Landing/LandingPage.css @@ -319,7 +319,7 @@ text-align: left; } -.stats-grid { +.landing-stats .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 2rem; @@ -327,8 +327,11 @@ margin: 0 auto; } -.stat-card { - text-align: left; +.landing-stats .stat-card { + display: flex; + align-items: center; + justify-content: center; + gap: 1.25rem; padding: 2rem; border-radius: 20px; border: 1px solid var(--border); @@ -336,30 +339,31 @@ transition: transform 0.3s ease, border 0.3s ease, box-shadow 0.3s ease; } -.stat-card:hover { +.landing-stats .stat-card:hover { transform: translateY(-6px); border-color: var(--accent); box-shadow: 0 20px 45px rgba(59, 130, 246, 0.18); } -.stat-number { +.landing-stats .stat-card .stat-number { font-size: 3rem; font-weight: 700; - margin-bottom: 0.5rem; background: linear-gradient(135deg, var(--accent), var(--accent-strong)); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; - text-align: left; - width: 167px; - display: flex; - flex-wrap: wrap; + line-height: 1; + text-align: center; + flex-shrink: 0; } -.stat-label { +.landing-stats .stat-card .stat-label { color: var(--text-muted); - font-size: 1.1rem; - text-align: left; + /* Keep original smaller stat-label sizing (avoid making the text feel “bigger” after layout fix) */ + font-size: 0.875rem; + margin: 0; + text-align: center; + max-width: 14ch; } /* Features */ From e9e9ed1571cdff4c8e11e1be19069192a3c03490 Mon Sep 17 00:00:00 2001 From: zoe Date: Wed, 24 Dec 2025 14:25:19 +1100 Subject: [PATCH 059/111] Resolve merge conflict in Evidence page --- frontend/.env | 4 +- frontend/{public => }/index.html | 10 +- frontend/package-lock.json | 18411 ++-------------- frontend/package.json | 22 +- frontend/postcss.config.js | 6 - frontend/src/{App.js => App.jsx} | 0 frontend/src/api/client.js | 2 +- ...ComplianceChart.js => ComplianceChart.jsx} | 0 .../components/{Dropdown.js => Dropdown.jsx} | 0 .../components/{Sidebar.js => Sidebar.jsx} | 0 .../{AuthContext.js => AuthContext.jsx} | 0 frontend/src/{index.js => main.jsx} | 5 +- .../Auth/{LoginPage.js => LoginPage.jsx} | 0 .../Auth/{SignUpPage.js => SignUpPage.jsx} | 0 .../{BrandPanel.js => BrandPanel.jsx} | 0 .../{LoginHeader.js => LoginHeader.jsx} | 0 .../{SignInPanel.js => SignInPanel.jsx} | 0 ...ConnectionsPage.js => ConnectionsPage.jsx} | 0 .../{ContactPage.js => ContactPage.jsx} | 0 .../{ContactForm.js => ContactForm.jsx} | 0 ...ContactInfoGrid.js => ContactInfoGrid.jsx} | 0 .../{FAQSection.js => FAQSection.jsx} | 0 .../src/pages/{Dashboard.js => Dashboard.jsx} | 0 .../src/pages/{Evidence.js => Evidence.jsx} | 40 + .../pages/Landing/{AboutUs.js => AboutUs.jsx} | 0 .../{LandingPage.js => LandingPage.jsx} | 0 ...BenefitsSection.js => BenefitsSection.jsx} | 0 .../{CTASection.js => CTASection.jsx} | 0 ...FeaturesSection.js => FeaturesSection.jsx} | 0 .../{HeroSection.js => HeroSection.jsx} | 0 .../{LandingFooter.js => LandingFooter.jsx} | 0 .../{LandingHeader.js => LandingHeader.jsx} | 0 .../{StatsSection.js => StatsSection.jsx} | 0 .../{ScanDetailPage.js => ScanDetailPage.jsx} | 0 .../Scans/{ScansPage.js => ScansPage.jsx} | 0 .../pages/{StyleGuide.js => StyleGuide.jsx} | 0 frontend/src/styles/global.css | 5 +- frontend/src/styles/tokens.css | 33 +- frontend/tailwind.config.js | 50 +- frontend/vite.config.js | 9 + 40 files changed, 1488 insertions(+), 17109 deletions(-) rename frontend/{public => }/index.html (83%) delete mode 100644 frontend/postcss.config.js rename frontend/src/{App.js => App.jsx} (100%) rename frontend/src/components/{ComplianceChart.js => ComplianceChart.jsx} (100%) rename frontend/src/components/{Dropdown.js => Dropdown.jsx} (100%) rename frontend/src/components/{Sidebar.js => Sidebar.jsx} (100%) rename frontend/src/context/{AuthContext.js => AuthContext.jsx} (100%) rename frontend/src/{index.js => main.jsx} (94%) rename frontend/src/pages/Auth/{LoginPage.js => LoginPage.jsx} (100%) rename frontend/src/pages/Auth/{SignUpPage.js => SignUpPage.jsx} (100%) rename frontend/src/pages/Auth/components/{BrandPanel.js => BrandPanel.jsx} (100%) rename frontend/src/pages/Auth/components/{LoginHeader.js => LoginHeader.jsx} (100%) rename frontend/src/pages/Auth/components/{SignInPanel.js => SignInPanel.jsx} (100%) rename frontend/src/pages/Connections/{ConnectionsPage.js => ConnectionsPage.jsx} (100%) rename frontend/src/pages/Contact/{ContactPage.js => ContactPage.jsx} (100%) rename frontend/src/pages/Contact/components/{ContactForm.js => ContactForm.jsx} (100%) rename frontend/src/pages/Contact/components/{ContactInfoGrid.js => ContactInfoGrid.jsx} (100%) rename frontend/src/pages/Contact/components/{FAQSection.js => FAQSection.jsx} (100%) rename frontend/src/pages/{Dashboard.js => Dashboard.jsx} (100%) rename frontend/src/pages/{Evidence.js => Evidence.jsx} (92%) rename frontend/src/pages/Landing/{AboutUs.js => AboutUs.jsx} (100%) rename frontend/src/pages/Landing/{LandingPage.js => LandingPage.jsx} (100%) rename frontend/src/pages/Landing/components/{BenefitsSection.js => BenefitsSection.jsx} (100%) rename frontend/src/pages/Landing/components/{CTASection.js => CTASection.jsx} (100%) rename frontend/src/pages/Landing/components/{FeaturesSection.js => FeaturesSection.jsx} (100%) rename frontend/src/pages/Landing/components/{HeroSection.js => HeroSection.jsx} (100%) rename frontend/src/pages/Landing/components/{LandingFooter.js => LandingFooter.jsx} (100%) rename frontend/src/pages/Landing/components/{LandingHeader.js => LandingHeader.jsx} (100%) rename frontend/src/pages/Landing/components/{StatsSection.js => StatsSection.jsx} (100%) rename frontend/src/pages/Scans/{ScanDetailPage.js => ScanDetailPage.jsx} (100%) rename frontend/src/pages/Scans/{ScansPage.js => ScansPage.jsx} (100%) rename frontend/src/pages/{StyleGuide.js => StyleGuide.jsx} (100%) create mode 100644 frontend/vite.config.js diff --git a/frontend/.env b/frontend/.env index 0e7798ef..c44733ca 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1,2 +1,4 @@ # Backend API URL -REACT_APP_API_URL=http://localhost:8000 +VITE_API_URL=http://localhost:8000 + + diff --git a/frontend/public/index.html b/frontend/index.html similarity index 83% rename from frontend/public/index.html rename to frontend/index.html index aa069f27..b864b995 100644 --- a/frontend/public/index.html +++ b/frontend/index.html @@ -2,19 +2,19 @@ - + - + - + + + + + React App diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 411ec4c1..04889c5c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,7 +10,6 @@ "dependencies": { "@fontsource/league-spartan": "^5.2.7", "@testing-library/dom": "^10.4.1", - "@testing-library/jest-dom": "^6.8.0", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^13.5.0", "chart.js": "^4.5.0", @@ -18,44 +17,11 @@ "react": "^19.1.1", "react-dom": "^19.1.1", "react-router-dom": "^7.9.1", - "react-scripts": "5.0.1", - "web-vitals": "^2.1.4" + "tailwindcss": "^4.1.18" }, "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^3.4.17" - } - }, - "node_modules/@adobe/css-tools": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", - "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", - "license": "MIT" - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" + "@vitejs/plugin-react": "^5.1.2", + "vite": "^7.3.0" } }, "node_modules/@babel/code-frame": { @@ -73,30 +39,33 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", - "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", - "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", + "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.3", - "@babel/parser": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -111,59 +80,15 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/eslint-parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.28.0.tgz", - "integrity": "sha512-N4ntErOlKvcbTt01rr5wj3y55xnIdx1ymrfIr8C2WnM1Y9glFgWaGDEULJIazOX3XM9NRzhfJ6zZnQ1sBNWU+w==", - "license": "MIT", - "dependencies": { - "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || >=14.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0", - "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "license": "Apache-2.0", - "engines": { - "node": ">=10" - } - }, - "node_modules/@babel/eslint-parser/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/generator": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -172,22 +97,11 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.27.2", @@ -200,113 +114,21 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", - "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", - "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "regexpu-core": "^6.2.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", - "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "debug": "^4.4.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.22.10" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -320,6 +142,7 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", @@ -333,87 +156,30 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-plugin-utils": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", - "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -423,45 +189,34 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", - "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helpers": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", - "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.28.2" + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", - "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.2" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -470,26 +225,27 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "node_modules/@babel/plugin-transform-react-jsx-self": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", - "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "node_modules/@babel/plugin-transform-react-jsx-source": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -498,17139 +254,1736 @@ "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" } }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", - "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.28.0.tgz", - "integrity": "sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-decorators": "^7.27.1" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", - "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", - "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-flow": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.27.1.tgz", - "integrity": "sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", - "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", - "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", - "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz", - "integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", - "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.3.tgz", - "integrity": "sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", - "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", - "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", - "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", - "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", - "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz", - "integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-flow": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", - "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", - "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", - "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz", - "integrity": "sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", - "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", - "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.27.1.tgz", - "integrity": "sha512-edoidOjl/ZxvYo4lSBOQGDSyToYVkTAwyVoa2tkuYTSmjrB1+uAedoL5iROVLXkxH+vRgA7uP4tMg2pUJpZ3Ug==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", - "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", - "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", - "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", - "license": "MIT", - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", - "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.3.tgz", - "integrity": "sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", - "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz", - "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", - "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", - "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", - "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", - "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", - "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", - "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", - "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.0", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.0", - "@babel/plugin-transform-async-to-generator": "^7.27.1", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.0", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.28.3", - "@babel/plugin-transform-classes": "^7.28.3", - "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", - "@babel/plugin-transform-dotall-regex": "^7.27.1", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.0", - "@babel/plugin-transform-exponentiation-operator": "^7.27.1", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.27.1", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.28.0", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.3", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "core-js-compat": "^3.43.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/preset-react": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz", - "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-transform-react-display-name": "^7.27.1", - "@babel/plugin-transform-react-jsx": "^7.27.1", - "@babel/plugin-transform-react-jsx-development": "^7.27.1", - "@babel/plugin-transform-react-pure-annotations": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", - "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", - "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", - "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.3", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.2", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", - "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "license": "MIT" - }, - "node_modules/@csstools/normalize.css": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.1.1.tgz", - "integrity": "sha512-YAYeJ+Xqh7fUou1d1j9XHl44BmsuThiTr4iNrgCQ3J27IbhXsxXDGZ1cXv8Qvs99d4rBbLiSKy3+WZiet32PcQ==", - "license": "CC0-1.0" - }, - "node_modules/@csstools/postcss-cascade-layers": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", - "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", - "license": "CC0-1.0", - "dependencies": { - "@csstools/selector-specificity": "^2.0.2", - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-color-function": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", - "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", - "license": "CC0-1.0", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-font-format-keywords": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", - "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", - "license": "CC0-1.0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-hwb-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", - "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", - "license": "CC0-1.0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-ic-unit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", - "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", - "license": "CC0-1.0", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", - "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", - "license": "CC0-1.0", - "dependencies": { - "@csstools/selector-specificity": "^2.0.0", - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-nested-calc": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", - "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", - "license": "CC0-1.0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-normalize-display-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", - "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", - "license": "CC0-1.0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-oklab-function": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", - "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", - "license": "CC0-1.0", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-progressive-custom-properties": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", - "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", - "license": "CC0-1.0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.3" - } - }, - "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", - "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", - "license": "CC0-1.0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", - "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", - "license": "CC0-1.0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", - "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", - "license": "CC0-1.0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-unset-value": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", - "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", - "license": "CC0-1.0", - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/selector-specificity": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", - "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", - "license": "CC0-1.0", - "engines": { - "node": "^14 || ^16 || >=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss-selector-parser": "^6.0.10" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.8.0.tgz", - "integrity": "sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==", - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@fontsource/league-spartan": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@fontsource/league-spartan/-/league-spartan-5.2.7.tgz", - "integrity": "sha512-OwYjMcJOGAGB680+4+Z47c/G5Ky8f8mT9Md6e8DB8/6fi1RGd/Ct5pUe3KT/lhUeNGUT8m5R4PKww47qtU8NHw==", - "license": "OFL-1.1", - "funding": { - "url": "https://github.com/sponsors/ayuhito" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "license": "BSD-3-Clause" - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", - "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", - "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/core": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", - "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", - "license": "MIT", - "dependencies": { - "@jest/console": "^27.5.1", - "@jest/reporters": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^27.5.1", - "jest-config": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-resolve-dependencies": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "jest-watcher": "^27.5.1", - "micromatch": "^4.0.4", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", - "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", - "license": "MIT", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/types": "^27.5.1", - "expect": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", - "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.1.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/reporters/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", - "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9", - "source-map": "^0.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/source-map/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jest/test-result": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", - "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", - "license": "MIT", - "dependencies": { - "@jest/console": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", - "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", - "license": "MIT", - "dependencies": { - "@jest/test-result": "^27.5.1", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-runtime": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", - "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.5.1", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-util": "^27.5.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT" - }, - "node_modules/@jest/transform/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.30", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", - "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@kurkle/color": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", - "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", - "license": "MIT" - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "license": "MIT" - }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "license": "MIT", - "dependencies": { - "eslint-scope": "5.1.1" - } - }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.17.tgz", - "integrity": "sha512-tXDyE1/jzFsHXjhRZQ3hMl0IVhYe5qula43LDWIhVfjp9G/nT5OQY5AORVOrkEGAUltBJOfOWeETbmhm6kHhuQ==", - "license": "MIT", - "dependencies": { - "ansi-html": "^0.0.9", - "core-js-pure": "^3.23.3", - "error-stack-parser": "^2.0.6", - "html-entities": "^2.1.0", - "loader-utils": "^2.0.4", - "schema-utils": "^4.2.0", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">= 10.13" - }, - "peerDependencies": { - "@types/webpack": "4.x || 5.x", - "react-refresh": ">=0.10.0 <1.0.0", - "sockjs-client": "^1.4.0", - "type-fest": ">=0.17.0 <5.0.0", - "webpack": ">=4.43.0 <6.0.0", - "webpack-dev-server": "3.x || 4.x || 5.x", - "webpack-hot-middleware": "2.x", - "webpack-plugin-serve": "0.x || 1.x" - }, - "peerDependenciesMeta": { - "@types/webpack": { - "optional": true - }, - "sockjs-client": { - "optional": true - }, - "type-fest": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - }, - "webpack-hot-middleware": { - "optional": true - }, - "webpack-plugin-serve": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-babel": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", - "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.10.4", - "@rollup/pluginutils": "^3.1.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "@types/babel__core": "^7.1.9", - "rollup": "^1.20.0||^2.0.0" - }, - "peerDependenciesMeta": { - "@types/babel__core": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", - "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "@types/resolve": "1.17.1", - "builtin-modules": "^3.1.0", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/plugin-replace": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", - "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "magic-string": "^0.25.7" - }, - "peerDependencies": { - "rollup": "^1.20.0 || ^2.0.0" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "license": "MIT", - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/pluginutils/node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "license": "MIT" - }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "license": "MIT" - }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.12.0.tgz", - "integrity": "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==", - "license": "MIT" - }, - "node_modules/@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@surma/rollup-plugin-off-main-thread": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", - "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", - "license": "Apache-2.0", - "dependencies": { - "ejs": "^3.1.6", - "json5": "^2.2.0", - "magic-string": "^0.25.0", - "string.prototype.matchall": "^4.0.6" - } - }, - "node_modules/@svgr/babel-plugin-add-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", - "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", - "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-svg-dynamic-title": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", - "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-svg-em-dimensions": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", - "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-transform-react-native-svg": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", - "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-transform-svg-component": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", - "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-preset": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", - "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", - "license": "MIT", - "dependencies": { - "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", - "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", - "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", - "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", - "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", - "@svgr/babel-plugin-transform-svg-component": "^5.5.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/core": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", - "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", - "license": "MIT", - "dependencies": { - "@svgr/plugin-jsx": "^5.5.0", - "camelcase": "^6.2.0", - "cosmiconfig": "^7.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/hast-util-to-babel-ast": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", - "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.12.6" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/plugin-jsx": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", - "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.12.3", - "@svgr/babel-preset": "^5.5.0", - "@svgr/hast-util-to-babel-ast": "^5.5.0", - "svg-parser": "^2.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/plugin-svgo": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", - "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", - "license": "MIT", - "dependencies": { - "cosmiconfig": "^7.0.0", - "deepmerge": "^4.2.2", - "svgo": "^1.2.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/webpack": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", - "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/plugin-transform-react-constant-elements": "^7.12.1", - "@babel/preset-env": "^7.12.1", - "@babel/preset-react": "^7.12.5", - "@svgr/core": "^5.5.0", - "@svgr/plugin-jsx": "^5.5.0", - "@svgr/plugin-svgo": "^5.5.0", - "loader-utils": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@testing-library/dom": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", - "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "picocolors": "1.1.1", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@testing-library/dom/node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/@testing-library/jest-dom": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.8.0.tgz", - "integrity": "sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==", - "license": "MIT", - "dependencies": { - "@adobe/css-tools": "^4.4.0", - "aria-query": "^5.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.6.3", - "picocolors": "^1.1.1", - "redent": "^3.0.0" - }, - "engines": { - "node": ">=14", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", - "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", - "license": "MIT" - }, - "node_modules/@testing-library/react": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", - "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@testing-library/dom": "^10.0.0", - "@types/react": "^18.0.0 || ^19.0.0", - "@types/react-dom": "^18.0.0 || ^19.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@testing-library/user-event": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", - "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - }, - "peerDependencies": { - "@testing-library/dom": ">=7.21.4" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "license": "ISC", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "license": "MIT" - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", - "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", - "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", - "license": "MIT", - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "node_modules/@types/eslint": { - "version": "8.56.12", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", - "integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==", - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" - }, - "node_modules/@types/express": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", - "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/express/node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", - "license": "MIT" - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "license": "MIT" - }, - "node_modules/@types/http-proxy": { - "version": "1.17.16", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", - "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT" - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "license": "MIT" - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", - "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.10.0" - } - }, - "node_modules/@types/node-forge": { - "version": "1.3.14", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", - "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "license": "MIT" - }, - "node_modules/@types/prettier": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", - "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", - "license": "MIT" - }, - "node_modules/@types/q": { - "version": "1.5.8", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz", - "integrity": "sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==", - "license": "MIT" - }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "license": "MIT" - }, - "node_modules/@types/resolve": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", - "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "license": "MIT" - }, - "node_modules/@types/semver": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", - "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-index": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", - "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", - "license": "MIT", - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", - "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, - "node_modules/@types/sockjs": { - "version": "0.3.36", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", - "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "license": "MIT" - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT" - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yargs": { - "version": "16.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", - "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz", - "integrity": "sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==", - "license": "MIT", - "dependencies": { - "@typescript-eslint/utils": "5.62.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "license": "ISC" - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0" - }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "deprecated": "Use your platform's native atob() and btoa() methods instead", - "license": "BSD-3-Clause" - }, - "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==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/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==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "license": "MIT", - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "license": "MIT", - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/address": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/adjust-sourcemap-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", - "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", - "license": "MIT", - "dependencies": { - "loader-utils": "^2.0.0", - "regex-parser": "^2.2.11" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-html": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.9.tgz", - "integrity": "sha512-ozbS3LuenHVxNRh/wdnN16QapUHzauqSomAl1jwwJRRsGwFwtj644lIhxfWu0Fy0acCij2+AEgHvjscq3dlVXg==", - "engines": [ - "node >= 0.8.0" - ], - "license": "Apache-2.0", - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "engines": [ - "node >= 0.8.0" - ], - "license": "Apache-2.0", - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "license": "MIT" - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "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/array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/array.prototype.findlast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", - "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", - "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-shim-unscopables": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.reduce": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.8.tgz", - "integrity": "sha512-DwuEqgXFBwbmZSRqt3BpQigWNUoqw9Ml2dTWdF3B2zQlQX4OeUE0zyuzX0fX0IbTvjdkZbcBTU3idgpO78qkTw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-array-method-boxes-properly": "^1.0.0", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "is-string": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", - "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "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/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "license": "MIT" - }, - "node_modules/ast-types-flow": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", - "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", - "license": "MIT" - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "license": "MIT" - }, - "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/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "license": "ISC", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "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/axe-core": { - "version": "4.10.3", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", - "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", - "license": "MPL-2.0", - "engines": { - "node": ">=4" - } - }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/babel-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", - "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", - "license": "MIT", - "dependencies": { - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.5.1", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-loader": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.4.1.tgz", - "integrity": "sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==", - "license": "MIT", - "dependencies": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.4", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 8.9" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "webpack": ">=2" - } - }, - "node_modules/babel-loader/node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "license": "BSD-3-Clause", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", - "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/babel-plugin-named-asset-import": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", - "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==", - "license": "MIT", - "peerDependencies": { - "@babel/core": "^7.1.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", - "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.7", - "@babel/helper-define-polyfill-provider": "^0.6.5", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", - "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-transform-react-remove-prop-types": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", - "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==", - "license": "MIT" - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/babel-preset-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", - "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^27.5.1", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-react-app": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.1.0.tgz", - "integrity": "sha512-f9B1xMdnkCIqe+2dHrJsoQFRz7reChaAHE/65SdaykPklQqhme2WaC08oD3is77x9ff98/9EazAKFDZv5rFEQg==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.16.0", - "@babel/plugin-proposal-class-properties": "^7.16.0", - "@babel/plugin-proposal-decorators": "^7.16.4", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", - "@babel/plugin-proposal-numeric-separator": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.0", - "@babel/plugin-proposal-private-methods": "^7.16.0", - "@babel/plugin-proposal-private-property-in-object": "^7.16.7", - "@babel/plugin-transform-flow-strip-types": "^7.16.0", - "@babel/plugin-transform-react-display-name": "^7.16.0", - "@babel/plugin-transform-runtime": "^7.16.4", - "@babel/preset-env": "^7.16.4", - "@babel/preset-react": "^7.16.0", - "@babel/preset-typescript": "^7.16.0", - "@babel/runtime": "^7.16.3", - "babel-plugin-macros": "^3.1.0", - "babel-plugin-transform-react-remove-prop-types": "^0.4.24" - } - }, - "node_modules/babel-preset-react-app/node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", - "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "license": "MIT" - }, - "node_modules/bfj": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.1.0.tgz", - "integrity": "sha512-I6MMLkn+anzNdCUp9hMRyui1HaNEUCco50lxbvNS4+EyXg8lN3nJ48PjPWtbH8UVS9CuMoaKE9U2V3l29DaRQw==", - "license": "MIT", - "dependencies": { - "bluebird": "^3.7.2", - "check-types": "^11.2.3", - "hoopy": "^0.1.4", - "jsonpath": "^1.1.1", - "tryer": "^1.0.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "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.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/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==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/body-parser/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/bonjour-service": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", - "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "license": "BSD-2-Clause" - }, - "node_modules/browserslist": { - "version": "4.25.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz", - "integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001737", - "electron-to-chromium": "^1.5.211", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "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", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "license": "MIT", - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001739", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz", - "integrity": "sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/case-sensitive-paths-webpack-plugin": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", - "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/chart.js": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", - "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", - "license": "MIT", - "dependencies": { - "@kurkle/color": "^0.3.0" - }, - "engines": { - "pnpm": ">=8" - } - }, - "node_modules/check-types": { - "version": "11.2.3", - "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", - "integrity": "sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==", - "license": "MIT" - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "license": "MIT" - }, - "node_modules/clean-css": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", - "license": "MIT", - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/clean-css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "license": "MIT", - "dependencies": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/coa/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/coa/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/coa/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/coa/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, - "node_modules/coa/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/coa/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/coa/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "license": "MIT" - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "license": "MIT", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/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/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "license": "MIT" - }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/core-js": { - "version": "3.45.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz", - "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat": { - "version": "3.45.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.1.tgz", - "integrity": "sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.25.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-pure": { - "version": "3.45.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.45.1.tgz", - "integrity": "sha512-OHnWFKgTUshEU8MK+lOs1H8kC8GkTi9Z1tvNkxrCcw9wl3MJIO7q2ld77wjWn4/xuGrVu2X+nME1iIIPBSdyEQ==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "license": "MIT", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/css-blank-pseudo": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", - "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", - "license": "CC0-1.0", - "dependencies": { - "postcss-selector-parser": "^6.0.9" - }, - "bin": { - "css-blank-pseudo": "dist/cli.cjs" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-declaration-sorter": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", - "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", - "license": "ISC", - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } - }, - "node_modules/css-has-pseudo": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", - "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", - "license": "CC0-1.0", - "dependencies": { - "postcss-selector-parser": "^6.0.9" - }, - "bin": { - "css-has-pseudo": "dist/cli.cjs" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-loader": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", - "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", - "license": "MIT", - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/css-minimizer-webpack-plugin": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", - "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", - "license": "MIT", - "dependencies": { - "cssnano": "^5.0.6", - "jest-worker": "^27.0.2", - "postcss": "^8.3.5", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@parcel/css": { - "optional": true - }, - "clean-css": { - "optional": true - }, - "csso": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/css-prefers-color-scheme": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", - "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", - "license": "CC0-1.0", - "bin": { - "css-prefers-color-scheme": "dist/cli.cjs" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", - "license": "MIT" - }, - "node_modules/css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/css-tree/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "license": "MIT" - }, - "node_modules/cssdb": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.11.2.tgz", - "integrity": "sha512-lhQ32TFkc1X4eTefGfYPvgovRSzIMofHkigfH8nWtyRL4XJLsRhJFreRvEgKzept7x1rjBuy3J/MurXLaFxW/A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - } - ], - "license": "CC0-1.0" - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano": { - "version": "5.1.15", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", - "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", - "license": "MIT", - "dependencies": { - "cssnano-preset-default": "^5.2.14", - "lilconfig": "^2.0.3", - "yaml": "^1.10.2" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/cssnano" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/cssnano-preset-default": { - "version": "5.2.14", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", - "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", - "license": "MIT", - "dependencies": { - "css-declaration-sorter": "^6.3.1", - "cssnano-utils": "^3.1.0", - "postcss-calc": "^8.2.3", - "postcss-colormin": "^5.3.1", - "postcss-convert-values": "^5.1.3", - "postcss-discard-comments": "^5.1.2", - "postcss-discard-duplicates": "^5.1.0", - "postcss-discard-empty": "^5.1.1", - "postcss-discard-overridden": "^5.1.0", - "postcss-merge-longhand": "^5.1.7", - "postcss-merge-rules": "^5.1.4", - "postcss-minify-font-values": "^5.1.0", - "postcss-minify-gradients": "^5.1.1", - "postcss-minify-params": "^5.1.4", - "postcss-minify-selectors": "^5.2.1", - "postcss-normalize-charset": "^5.1.0", - "postcss-normalize-display-values": "^5.1.0", - "postcss-normalize-positions": "^5.1.1", - "postcss-normalize-repeat-style": "^5.1.1", - "postcss-normalize-string": "^5.1.0", - "postcss-normalize-timing-functions": "^5.1.0", - "postcss-normalize-unicode": "^5.1.1", - "postcss-normalize-url": "^5.1.0", - "postcss-normalize-whitespace": "^5.1.1", - "postcss-ordered-values": "^5.1.3", - "postcss-reduce-initial": "^5.1.2", - "postcss-reduce-transforms": "^5.1.0", - "postcss-svgo": "^5.1.0", - "postcss-unique-selectors": "^5.1.1" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/cssnano-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", - "license": "MIT", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", - "license": "MIT", - "dependencies": { - "css-tree": "^1.1.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", - "license": "CC0-1.0" - }, - "node_modules/csso/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "license": "MIT" - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "license": "MIT", - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "license": "MIT" - }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "license": "BSD-2-Clause" - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "license": "MIT", - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "license": "MIT" - }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "license": "MIT" - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "license": "BSD-2-Clause", - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "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" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "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" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "license": "MIT" - }, - "node_modules/detect-port-alt": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", - "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", - "license": "MIT", - "dependencies": { - "address": "^1.0.1", - "debug": "^2.6.0" - }, - "bin": { - "detect": "bin/detect-port", - "detect-port": "bin/detect-port" - }, - "engines": { - "node": ">= 4.2.1" - } - }, - "node_modules/detect-port-alt/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/detect-port-alt/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/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "license": "Apache-2.0" - }, - "node_modules/diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", - "license": "MIT", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "license": "MIT" - }, - "node_modules/dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", - "license": "MIT", - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "license": "MIT" - }, - "node_modules/dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "license": "MIT", - "dependencies": { - "utila": "~0.4" - } - }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "deprecated": "Use your platform's native DOMException instead", - "license": "MIT", - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=10" - } - }, - "node_modules/dotenv-expand": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", - "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", - "license": "BSD-2-Clause" - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "license": "MIT" - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.213", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.213.tgz", - "integrity": "sha512-xr9eRzSLNa4neDO0xVFrkXu3vyIzG4Ay08dApecw42Z1NbmCt+keEpXdvlYGVe0wtvY5dhW0Ay0lY0IOfsCg0Q==", - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "license": "MIT", - "dependencies": { - "stackframe": "^1.3.4" - } - }, - "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", - "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-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "license": "MIT" - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-iterator-helpers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", - "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.3", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.6", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "iterator.prototype": "^1.1.4", - "safe-array-concat": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "license": "MIT" - }, - "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==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "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-shim-unscopables": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", - "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", - "license": "MIT", - "dependencies": { - "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", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-react-app": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", - "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.16.0", - "@babel/eslint-parser": "^7.16.3", - "@rushstack/eslint-patch": "^1.1.0", - "@typescript-eslint/eslint-plugin": "^5.5.0", - "@typescript-eslint/parser": "^5.5.0", - "babel-preset-react-app": "^10.0.1", - "confusing-browser-globals": "^1.0.11", - "eslint-plugin-flowtype": "^8.0.3", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jest": "^25.3.0", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.27.1", - "eslint-plugin-react-hooks": "^4.3.0", - "eslint-plugin-testing-library": "^5.0.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "eslint": "^8.0.0" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "license": "MIT", - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", - "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", - "license": "MIT", - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-flowtype": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", - "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", - "license": "BSD-3-Clause", - "dependencies": { - "lodash": "^4.17.21", - "string-natural-compare": "^3.0.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@babel/plugin-syntax-flow": "^7.14.5", - "@babel/plugin-transform-react-jsx": "^7.14.9", - "eslint": "^8.1.0" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", - "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", - "license": "MIT", - "dependencies": { - "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.9", - "array.prototype.findlastindex": "^1.2.6", - "array.prototype.flat": "^1.3.3", - "array.prototype.flatmap": "^1.3.3", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.1", - "hasown": "^2.0.2", - "is-core-module": "^2.16.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.1", - "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.9", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-jest": { - "version": "25.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", - "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", - "license": "MIT", - "dependencies": { - "@typescript-eslint/experimental-utils": "^5.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^4.0.0 || ^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - }, - "jest": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", - "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", - "license": "MIT", - "dependencies": { - "aria-query": "^5.3.2", - "array-includes": "^3.1.8", - "array.prototype.flatmap": "^1.3.2", - "ast-types-flow": "^0.0.8", - "axe-core": "^4.10.0", - "axobject-query": "^4.1.0", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "hasown": "^2.0.2", - "jsx-ast-utils": "^3.3.5", - "language-tags": "^1.0.9", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "safe-regex-test": "^1.0.3", - "string.prototype.includes": "^2.0.1" - }, - "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" - } - }, - "node_modules/eslint-plugin-react": { - "version": "7.37.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", - "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.8", - "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.3", - "array.prototype.tosorted": "^1.1.4", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.2.1", - "estraverse": "^5.3.0", - "hasown": "^2.0.2", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.9", - "object.fromentries": "^2.0.8", - "object.values": "^1.2.1", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.12", - "string.prototype.repeat": "^1.0.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", - "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-plugin-react/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-testing-library": { - "version": "5.11.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.1.tgz", - "integrity": "sha512-5eX9e1Kc2PqVRed3taaLnAAqPZGEX75C+M/rXzUAI3wIg/ZxzUm1OVAwfe/O+vE+6YXOLetSe9g5GKD2ecXipw==", - "license": "MIT", - "dependencies": { - "@typescript-eslint/utils": "^5.58.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0", - "npm": ">=6" - }, - "peerDependencies": { - "eslint": "^7.5.0 || ^8.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-webpack-plugin": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz", - "integrity": "sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==", - "license": "MIT", - "dependencies": { - "@types/eslint": "^7.29.0 || ^8.4.1", - "jest-worker": "^28.0.2", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0", - "webpack": "^5.0.0" - } - }, - "node_modules/eslint-webpack-plugin/node_modules/jest-worker": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", - "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/eslint-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "license": "MIT" - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "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.13.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" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/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/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "license": "Apache-2.0", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", - "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", - "license": "MIT", - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/file-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/filesize": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", - "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "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.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/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/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "license": "MIT", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "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" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fork-ts-checker-webpack-plugin": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", - "integrity": "sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.8.3", - "@types/json-schema": "^7.0.5", - "chalk": "^4.1.0", - "chokidar": "^3.4.2", - "cosmiconfig": "^6.0.0", - "deepmerge": "^4.2.2", - "fs-extra": "^9.0.0", - "glob": "^7.1.6", - "memfs": "^3.1.2", - "minimatch": "^3.0.4", - "schema-utils": "2.7.0", - "semver": "^7.3.2", - "tapable": "^1.0.0" - }, - "engines": { - "node": ">=10", - "yarn": ">=1.0.0" - }, - "peerDependencies": { - "eslint": ">= 6", - "typescript": ">= 2.7", - "vue-template-compiler": "*", - "webpack": ">= 4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - }, - "vue-template-compiler": { - "optional": true - } - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "license": "MIT", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/form-data": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", - "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.35" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/fs-monkey": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", - "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", - "license": "Unlicense" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "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/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "license": "ISC" - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause" - }, - "node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "license": "MIT", - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "license": "MIT", - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "license": "MIT" - }, - "node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "license": "MIT", - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "license": "MIT" - }, - "node_modules/harmony-reflect": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", - "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==", - "license": "(Apache-2.0 OR MPL-1.1)" - }, - "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": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "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", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/hoopy": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", - "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "license": "MIT", - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/html-entities": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", - "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ], - "license": "MIT" - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "license": "MIT" - }, - "node_modules/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "license": "MIT", - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/html-webpack-plugin": { - "version": "5.6.4", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.4.tgz", - "integrity": "sha512-V/PZeWsqhfpE27nKeX9EO2sbR+D17A+tLf6qU+ht66jdUsN0QLKJN27Z+1+gHrVMKgndBahes0PU6rRihDgHTw==", - "license": "MIT", - "dependencies": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/html-webpack-plugin" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.20.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "license": "MIT" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", - "license": "MIT" - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "license": "MIT", - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-proxy-middleware": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", - "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", - "license": "MIT", - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "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_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "license": "ISC", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", - "license": "ISC" - }, - "node_modules/identity-obj-proxy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", - "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", - "license": "MIT", - "dependencies": { - "harmony-reflect": "^1.4.6" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/immer": { - "version": "9.0.21", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", - "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "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==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", - "license": "MIT", - "engines": { - "node": ">= 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-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT" - }, - "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", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "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", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "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==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "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-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", - "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", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "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==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "license": "MIT" - }, - "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" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "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-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "license": "MIT" - }, - "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-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-root": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", - "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "license": "MIT" - }, - "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-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/iterator.prototype": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", - "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "get-proto": "^1.0.0", - "has-symbols": "^1.1.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jake": { - "version": "10.9.4", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", - "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.6", - "filelist": "^1.0.4", - "picocolors": "^1.1.1" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", - "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", - "license": "MIT", - "dependencies": { - "@jest/core": "^27.5.1", - "import-local": "^3.0.2", - "jest-cli": "^27.5.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", - "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "execa": "^5.0.0", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", - "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", - "license": "MIT", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.5.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-cli": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", - "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", - "license": "MIT", - "dependencies": { - "@jest/core": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "prompts": "^2.0.1", - "yargs": "^16.2.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", - "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.8.0", - "@jest/test-sequencer": "^27.5.1", - "@jest/types": "^27.5.1", - "babel-jest": "^27.5.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.9", - "jest-circus": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-jasmine2": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", - "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", - "license": "MIT", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", - "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", - "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", - "license": "MIT", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1", - "jsdom": "^16.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", - "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", - "license": "MIT", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "license": "MIT", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-jasmine2": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", - "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", - "license": "MIT", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.5.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-leak-detector": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", - "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", - "license": "MIT", - "dependencies": { - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", - "license": "MIT", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", - "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", - "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-snapshot": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runner": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", - "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", - "license": "MIT", - "dependencies": { - "@jest/console": "^27.5.1", - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-leak-detector": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", - "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", - "license": "MIT", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/globals": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-serializer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", - "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", - "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^27.5.1", - "graceful-fs": "^4.2.9", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^27.5.1", - "semver": "^7.3.2" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", - "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "leven": "^3.1.0", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-watch-typeahead": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-1.1.0.tgz", - "integrity": "sha512-Va5nLSJTN7YFtC2jd+7wsoe1pNe5K4ShLux/E5iHEwlB9AxaxmggY7to9KUqKojhaJw3aXqt5WAb4jGPOolpEw==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.3.1", - "chalk": "^4.0.0", - "jest-regex-util": "^28.0.0", - "jest-watcher": "^28.0.0", - "slash": "^4.0.0", - "string-length": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "jest": "^27.0.0 || ^28.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/@jest/console": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", - "license": "MIT", - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/@jest/console/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watch-typeahead/node_modules/@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", - "license": "MIT", - "dependencies": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-watch-typeahead/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-message-util/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", - "license": "MIT", - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "license": "MIT", - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-watcher": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", - "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", - "license": "MIT", - "dependencies": { - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^28.1.3", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watch-typeahead/node_modules/pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" - }, - "node_modules/jest-watch-typeahead/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watch-typeahead/node_modules/string-length": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", - "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", - "license": "MIT", - "dependencies": { - "char-regex": "^2.0.0", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watch-typeahead/node_modules/string-length/node_modules/char-regex": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.2.tgz", - "integrity": "sha512-cbGOjAptfM2LVmWhwRFHEKTPkLwNddVmuqYZQt895yXwAsWsXObCG+YN4DGQ/JBtT4GP1a1lPPdio2z413LmTg==", - "license": "MIT", - "engines": { - "node": ">=12.20" - } - }, - "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", - "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/jest-watcher": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", - "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", - "license": "MIT", - "dependencies": { - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^27.5.1", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "license": "MIT", - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "license": "MIT", - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT" - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonpath": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", - "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", - "license": "MIT", - "dependencies": { - "esprima": "1.2.2", - "static-eval": "2.0.2", - "underscore": "1.12.1" - } - }, - "node_modules/jsonpath/node_modules/esprima": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", - "integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/jsonpointer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", - "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/klona": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", - "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/language-subtag-registry": { - "version": "0.3.23", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", - "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", - "license": "CC0-1.0" - }, - "node_modules/language-tags": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", - "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", - "license": "MIT", - "dependencies": { - "language-subtag-registry": "^0.3.20" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/launch-editor": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz", - "integrity": "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==", - "license": "MIT", - "dependencies": { - "picocolors": "^1.1.1", - "shell-quote": "^1.8.3" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT" - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "license": "MIT", - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "license": "MIT", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "license": "MIT" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "license": "MIT" - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "license": "MIT" - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "license": "MIT" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/lucide-react": { - "version": "0.542.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.542.0.tgz", - "integrity": "sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "license": "MIT", - "bin": { - "lz-string": "bin/bin.js" - } - }, - "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "license": "MIT", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", - "license": "CC0-1.0" - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memfs": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", - "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", - "license": "Unlicense", - "dependencies": { - "fs-monkey": "^1.0.4" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "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==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "engines": { - "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", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "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", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/mini-css-extract-plugin": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz", - "integrity": "sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==", - "license": "MIT", - "dependencies": { - "schema-utils": "^4.0.0", - "tapable": "^2.2.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "license": "ISC" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "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/multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "license": "MIT", - "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "license": "MIT" - }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT" - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "license": "MIT", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/nwsapi": { - "version": "2.2.21", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.21.tgz", - "integrity": "sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==", - "license": "MIT" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "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/object.entries": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", - "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.8.tgz", - "integrity": "sha512-qkHIGe4q0lSYMv0XI4SsBTJz3WaURhLvd0lKSgtVuOsJ2krg4SgMw3PIRQFMp07yi++UR3se2mkcLqsBNpBb/A==", - "license": "MIT", - "dependencies": { - "array.prototype.reduce": "^1.0.6", - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "gopd": "^1.0.1", - "safe-array-concat": "^1.1.2" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.values": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", - "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "license": "MIT" - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "license": "MIT", - "engines": { - "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/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "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/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "license": "MIT", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "license": "MIT", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "license": "MIT" - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "license": "MIT", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-up/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "license": "MIT", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "license": "MIT", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "license": "MIT", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "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.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-attribute-case-insensitive": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", - "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-browser-comments": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz", - "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==", - "license": "CC0-1.0", - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "browserslist": ">=4", - "postcss": ">=8" - } - }, - "node_modules/postcss-calc": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", - "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.9", - "postcss-value-parser": "^4.2.0" - }, - "peerDependencies": { - "postcss": "^8.2.2" - } - }, - "node_modules/postcss-clamp": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", - "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=7.6.0" - }, - "peerDependencies": { - "postcss": "^8.4.6" - } - }, - "node_modules/postcss-color-functional-notation": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", - "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", - "license": "CC0-1.0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-color-hex-alpha": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", - "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-color-rebeccapurple": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", - "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", - "license": "CC0-1.0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-colormin": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", - "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.21.4", - "caniuse-api": "^3.0.0", - "colord": "^2.9.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-convert-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", - "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.21.4", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-custom-media": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", - "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.3" - } - }, - "node_modules/postcss-custom-properties": { - "version": "12.1.11", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.11.tgz", - "integrity": "sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-custom-selectors": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", - "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.3" - } - }, - "node_modules/postcss-dir-pseudo-class": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", - "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", - "license": "CC0-1.0", - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-discard-comments": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", - "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", - "license": "MIT", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-duplicates": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "license": "MIT", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-empty": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", - "license": "MIT", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-overridden": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", - "license": "MIT", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-double-position-gradients": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", - "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", - "license": "CC0-1.0", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-env-function": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", - "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", - "license": "CC0-1.0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-flexbugs-fixes": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz", - "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==", - "license": "MIT", - "peerDependencies": { - "postcss": "^8.1.4" - } - }, - "node_modules/postcss-focus-visible": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", - "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", - "license": "CC0-1.0", - "dependencies": { - "postcss-selector-parser": "^6.0.9" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-within": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", - "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", - "license": "CC0-1.0", - "dependencies": { - "postcss-selector-parser": "^6.0.9" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-font-variant": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", - "license": "MIT", - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-gap-properties": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", - "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", - "license": "CC0-1.0", - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-image-set-function": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", - "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", - "license": "CC0-1.0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-initial": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", - "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", - "license": "MIT", - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "license": "MIT", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-lab-function": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", - "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", - "license": "CC0-1.0", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-load-config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.0.0", - "yaml": "^2.3.4" - }, - "engines": { - "node": ">= 14" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/postcss-load-config/node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, - "node_modules/postcss-loader": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", - "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", - "license": "MIT", - "dependencies": { - "cosmiconfig": "^7.0.0", - "klona": "^2.0.5", - "semver": "^7.3.5" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" - } - }, - "node_modules/postcss-logical": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", - "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", - "license": "CC0-1.0", - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-media-minmax": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", - "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-merge-longhand": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", - "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^5.1.1" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-merge-rules": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", - "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.21.4", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^3.1.0", - "postcss-selector-parser": "^6.0.5" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-font-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", - "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-gradients": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", - "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", - "license": "MIT", - "dependencies": { - "colord": "^2.9.1", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-params": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", - "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.21.4", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-selectors": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", - "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.5" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", - "license": "ISC", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", - "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", - "license": "MIT", - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-modules-scope": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", - "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", - "license": "ISC", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "license": "ISC", - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-nested": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", - "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-nesting": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", - "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", - "license": "CC0-1.0", - "dependencies": { - "@csstools/selector-specificity": "^2.0.0", - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-normalize": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-10.0.1.tgz", - "integrity": "sha512-+5w18/rDev5mqERcG3W5GZNMJa1eoYYNGo8gB7tEwaos0ajk3ZXAI4mHGcNT47NE+ZnZD1pEpUOFLvltIwmeJA==", - "license": "CC0-1.0", - "dependencies": { - "@csstools/normalize.css": "*", - "postcss-browser-comments": "^4", - "sanitize.css": "*" - }, - "engines": { - "node": ">= 12" - }, - "peerDependencies": { - "browserslist": ">= 4", - "postcss": ">= 8" - } - }, - "node_modules/postcss-normalize-charset": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", - "license": "MIT", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-display-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", - "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-positions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", - "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-repeat-style": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", - "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-string": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", - "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-timing-functions": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", - "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-unicode": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", - "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.21.4", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", - "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", - "license": "MIT", - "dependencies": { - "normalize-url": "^6.0.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-whitespace": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", - "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-opacity-percentage": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz", - "integrity": "sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==", - "funding": [ - { - "type": "kofi", - "url": "https://ko-fi.com/mrcgrtz" - }, - { - "type": "liberapay", - "url": "https://liberapay.com/mrcgrtz" - } - ], - "license": "MIT", - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-ordered-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", - "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", - "license": "MIT", - "dependencies": { - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-overflow-shorthand": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", - "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", - "license": "CC0-1.0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-page-break": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", - "license": "MIT", - "peerDependencies": { - "postcss": "^8" - } - }, - "node_modules/postcss-place": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", - "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", - "license": "CC0-1.0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-preset-env": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.3.tgz", - "integrity": "sha512-T1LgRm5uEVFSEF83vHZJV2z19lHg4yJuZ6gXZZkqVsqv63nlr6zabMH3l4Pc01FQCyfWVrh2GaUeCVy9Po+Aag==", - "license": "CC0-1.0", - "dependencies": { - "@csstools/postcss-cascade-layers": "^1.1.1", - "@csstools/postcss-color-function": "^1.1.1", - "@csstools/postcss-font-format-keywords": "^1.0.1", - "@csstools/postcss-hwb-function": "^1.0.2", - "@csstools/postcss-ic-unit": "^1.0.1", - "@csstools/postcss-is-pseudo-class": "^2.0.7", - "@csstools/postcss-nested-calc": "^1.0.0", - "@csstools/postcss-normalize-display-values": "^1.0.1", - "@csstools/postcss-oklab-function": "^1.1.1", - "@csstools/postcss-progressive-custom-properties": "^1.3.0", - "@csstools/postcss-stepped-value-functions": "^1.0.1", - "@csstools/postcss-text-decoration-shorthand": "^1.0.0", - "@csstools/postcss-trigonometric-functions": "^1.0.2", - "@csstools/postcss-unset-value": "^1.0.2", - "autoprefixer": "^10.4.13", - "browserslist": "^4.21.4", - "css-blank-pseudo": "^3.0.3", - "css-has-pseudo": "^3.0.4", - "css-prefers-color-scheme": "^6.0.3", - "cssdb": "^7.1.0", - "postcss-attribute-case-insensitive": "^5.0.2", - "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^4.2.4", - "postcss-color-hex-alpha": "^8.0.4", - "postcss-color-rebeccapurple": "^7.1.1", - "postcss-custom-media": "^8.0.2", - "postcss-custom-properties": "^12.1.10", - "postcss-custom-selectors": "^6.0.3", - "postcss-dir-pseudo-class": "^6.0.5", - "postcss-double-position-gradients": "^3.1.2", - "postcss-env-function": "^4.0.6", - "postcss-focus-visible": "^6.0.4", - "postcss-focus-within": "^5.0.4", - "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^3.0.5", - "postcss-image-set-function": "^4.0.7", - "postcss-initial": "^4.0.1", - "postcss-lab-function": "^4.2.1", - "postcss-logical": "^5.0.4", - "postcss-media-minmax": "^5.0.0", - "postcss-nesting": "^10.2.0", - "postcss-opacity-percentage": "^1.1.2", - "postcss-overflow-shorthand": "^3.0.4", - "postcss-page-break": "^3.0.4", - "postcss-place": "^7.0.5", - "postcss-pseudo-class-any-link": "^7.1.6", - "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^6.0.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-pseudo-class-any-link": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", - "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", - "license": "CC0-1.0", - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-reduce-initial": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", - "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.21.4", - "caniuse-api": "^3.0.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-reduce-transforms": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", - "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-replace-overflow-wrap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", - "license": "MIT", - "peerDependencies": { - "postcss": "^8.0.3" - } - }, - "node_modules/postcss-selector-not": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", - "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-svgo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", - "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "svgo": "^2.7.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-svgo/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/postcss-svgo/node_modules/css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/postcss-svgo/node_modules/mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", - "license": "CC0-1.0" - }, - "node_modules/postcss-svgo/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-svgo/node_modules/svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", - "license": "MIT", - "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/postcss-unique-selectors": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", - "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.5" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT" - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "license": "MIT", - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } - }, - "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, - "node_modules/promise": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "license": "MIT", - "dependencies": { - "asap": "~2.0.6" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/psl": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/lupomontero" - } - }, - "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/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", - "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", - "license": "MIT", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "license": "MIT" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "license": "MIT", - "dependencies": { - "performance-now": "^2.1.0" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/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==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", - "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-app-polyfill": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", - "integrity": "sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w==", - "license": "MIT", - "dependencies": { - "core-js": "^3.19.2", - "object-assign": "^4.1.1", - "promise": "^8.1.0", - "raf": "^3.4.1", - "regenerator-runtime": "^0.13.9", - "whatwg-fetch": "^3.6.2" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/react-dev-utils": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", - "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.16.0", - "address": "^1.1.2", - "browserslist": "^4.18.1", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.3", - "detect-port-alt": "^1.1.6", - "escape-string-regexp": "^4.0.0", - "filesize": "^8.0.6", - "find-up": "^5.0.0", - "fork-ts-checker-webpack-plugin": "^6.5.0", - "global-modules": "^2.0.0", - "globby": "^11.0.4", - "gzip-size": "^6.0.0", - "immer": "^9.0.7", - "is-root": "^2.1.0", - "loader-utils": "^3.2.0", - "open": "^8.4.0", - "pkg-up": "^3.1.0", - "prompts": "^2.4.2", - "react-error-overlay": "^6.0.11", - "recursive-readdir": "^2.2.2", - "shell-quote": "^1.7.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/react-dev-utils/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-dev-utils/node_modules/loader-utils": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", - "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", - "license": "MIT", - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/react-dev-utils/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-dev-utils/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-dev-utils/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-dom": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", - "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.26.0" - }, - "peerDependencies": { - "react": "^19.1.1" - } - }, - "node_modules/react-error-overlay": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.1.0.tgz", - "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", - "license": "MIT" - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "license": "MIT" - }, - "node_modules/react-refresh": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", - "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-router": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.1.tgz", - "integrity": "sha512-pfAByjcTpX55mqSDGwGnY9vDCpxqBLASg0BMNAuMmpSGESo/TaOUG6BllhAtAkCGx8Rnohik/XtaqiYUJtgW2g==", - "license": "MIT", - "dependencies": { - "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } - } - }, - "node_modules/react-router-dom": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.1.tgz", - "integrity": "sha512-U9WBQssBE9B1vmRjo9qTM7YRzfZ3lUxESIZnsf4VjR/lXYz9MHjvOxHzr/aUm4efpktbVOrF09rL/y4VHa8RMw==", - "license": "MIT", - "dependencies": { - "react-router": "7.9.1" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - } - }, - "node_modules/react-router/node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/react-scripts": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", - "integrity": "sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.16.0", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", - "@svgr/webpack": "^5.5.0", - "babel-jest": "^27.4.2", - "babel-loader": "^8.2.3", - "babel-plugin-named-asset-import": "^0.3.8", - "babel-preset-react-app": "^10.0.1", - "bfj": "^7.0.2", - "browserslist": "^4.18.1", - "camelcase": "^6.2.1", - "case-sensitive-paths-webpack-plugin": "^2.4.0", - "css-loader": "^6.5.1", - "css-minimizer-webpack-plugin": "^3.2.0", - "dotenv": "^10.0.0", - "dotenv-expand": "^5.1.0", - "eslint": "^8.3.0", - "eslint-config-react-app": "^7.0.1", - "eslint-webpack-plugin": "^3.1.1", - "file-loader": "^6.2.0", - "fs-extra": "^10.0.0", - "html-webpack-plugin": "^5.5.0", - "identity-obj-proxy": "^3.0.0", - "jest": "^27.4.3", - "jest-resolve": "^27.4.2", - "jest-watch-typeahead": "^1.0.0", - "mini-css-extract-plugin": "^2.4.5", - "postcss": "^8.4.4", - "postcss-flexbugs-fixes": "^5.0.2", - "postcss-loader": "^6.2.1", - "postcss-normalize": "^10.0.1", - "postcss-preset-env": "^7.0.1", - "prompts": "^2.4.2", - "react-app-polyfill": "^3.0.0", - "react-dev-utils": "^12.0.1", - "react-refresh": "^0.11.0", - "resolve": "^1.20.0", - "resolve-url-loader": "^4.0.0", - "sass-loader": "^12.3.0", - "semver": "^7.3.5", - "source-map-loader": "^3.0.0", - "style-loader": "^3.3.1", - "tailwindcss": "^3.0.2", - "terser-webpack-plugin": "^5.2.5", - "webpack": "^5.64.4", - "webpack-dev-server": "^4.6.0", - "webpack-manifest-plugin": "^4.0.2", - "workbox-webpack-plugin": "^6.4.1" - }, - "bin": { - "react-scripts": "bin/react-scripts.js" - }, - "engines": { - "node": ">=14.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - }, - "peerDependencies": { - "react": ">= 16", - "typescript": "^3.2.1 || ^4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "license": "MIT", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/recursive-readdir": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", - "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", - "license": "MIT", - "dependencies": { - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "license": "MIT", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "license": "MIT" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "license": "MIT" - }, - "node_modules/regex-parser": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.1.tgz", - "integrity": "sha512-yXLRqatcCuKtVHsWrNg0JL3l1zGfdXeEvDa0bdu4tCDQw0RpMDZsqbkyRTUnKMR0tXF627V2oEWjBEaEdqTwtQ==", - "license": "MIT" - }, - "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/regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", - "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "license": "MIT" - }, - "node_modules/regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~3.0.2" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/renderkid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "license": "MIT", - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "license": "MIT" - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-url-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz", - "integrity": "sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA==", - "license": "MIT", - "dependencies": { - "adjust-sourcemap-loader": "^4.0.0", - "convert-source-map": "^1.7.0", - "loader-utils": "^2.0.0", - "postcss": "^7.0.35", - "source-map": "0.6.1" - }, - "engines": { - "node": ">=8.9" - }, - "peerDependencies": { - "rework": "1.0.1", - "rework-visit": "1.0.0" - }, - "peerDependenciesMeta": { - "rework": { - "optional": true - }, - "rework-visit": { - "optional": true - } - } - }, - "node_modules/resolve-url-loader/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT" - }, - "node_modules/resolve-url-loader/node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "license": "ISC" - }, - "node_modules/resolve-url-loader/node_modules/postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "license": "MIT", - "dependencies": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/resolve-url-loader/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve.exports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", - "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "2.79.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", - "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/rollup-plugin-terser": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", - "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", - "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "jest-worker": "^26.2.1", - "serialize-javascript": "^4.0.0", - "terser": "^5.0.0" - }, - "peerDependencies": { - "rollup": "^2.0.0" - } - }, - "node_modules/rollup-plugin-terser/node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/rollup-plugin-terser/node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "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", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "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", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/sanitize.css": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", - "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==", - "license": "CC0-1.0" - }, - "node_modules/sass-loader": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", - "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", - "license": "MIT", - "dependencies": { - "klona": "^2.0.4", - "neo-async": "^2.6.2" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "fibers": ">= 3.1.0", - "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", - "sass": "^1.3.0", - "sass-embedded": "*", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "fibers": { - "optional": true - }, - "node-sass": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - } - } - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "license": "ISC" - }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", - "license": "MIT" - }, - "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/schema-utils/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/schema-utils/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "license": "MIT" - }, - "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", - "license": "MIT", - "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/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/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "license": "MIT", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "license": "ISC" - }, - "node_modules/serve-index/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/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "license": "ISC" - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "license": "MIT" - }, - "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" - }, - "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" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "license": "MIT" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "license": "MIT", - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "license": "MIT" - }, - "node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-loader": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.2.tgz", - "integrity": "sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==", - "license": "MIT", - "dependencies": { - "abab": "^2.0.5", - "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "license": "MIT" - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause" - }, - "node_modules/stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", - "license": "MIT" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "license": "MIT" - }, - "node_modules/static-eval": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", - "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", - "license": "MIT", - "dependencies": { - "escodegen": "^1.8.1" - } - }, - "node_modules/static-eval/node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/static-eval/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/static-eval/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "license": "MIT", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-eval/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "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/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-natural-compare": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", - "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==", - "license": "MIT" - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/string-width/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/string.prototype.includes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", - "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", - "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "regexp.prototype.flags": "^1.5.3", - "set-function-name": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.repeat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", - "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", - "license": "MIT", - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "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/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "license": "BSD-2-Clause", - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", - "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "license": "MIT", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/style-loader": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", - "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", - "license": "MIT", - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/stylehacks": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", - "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.21.4", - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/sucrase": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "^10.3.10", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/sucrase/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/sucrase/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/sucrase/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sucrase/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, + "node_modules/@fontsource/league-spartan": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource/league-spartan/-/league-spartan-5.2.8.tgz", + "integrity": "sha512-83zacDd4BcvLwQ5JLjMg9s81MybTGKLsQJTihXSy4iQZEhMOofIvfhpe6udE7xk4l29NaQCK59DAzv1HfdIbLw==", + "license": "OFL-1.1", "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/ayuhito" } }, - "node_modules/svg-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", - "license": "MIT" - }, - "node_modules/svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, "license": "MIT", "dependencies": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=4.0.0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/svgo/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/svgo/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, "engines": { - "node": ">=4" - } - }, - "node_modules/svgo/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" + "node": ">=6.0.0" } }, - "node_modules/svgo/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, "license": "MIT" }, - "node_modules/svgo/node_modules/css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "node_modules/svgo/node_modules/css-what": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", - "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/svgo/node_modules/dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - } - }, - "node_modules/svgo/node_modules/domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "node_modules/svgo/node_modules/domutils/node_modules/domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "license": "BSD-2-Clause" - }, - "node_modules/svgo/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/svgo/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/svgo/node_modules/nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "~1.0.0" - } - }, - "node_modules/svgo/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", "license": "MIT" }, - "node_modules/tailwindcss": { - "version": "3.4.17", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", - "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.6", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true, + "license": "MIT" }, - "node_modules/tailwindcss/node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.5.tgz", + "integrity": "sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/tapable": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", - "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.5.tgz", + "integrity": "sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.5.tgz", + "integrity": "sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/tempy": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", - "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.5.tgz", + "integrity": "sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.5.tgz", + "integrity": "sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", - "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.5.tgz", + "integrity": "sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT" - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "license": "MIT" - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.5.tgz", + "integrity": "sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.5.tgz", + "integrity": "sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/throat": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz", - "integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==", - "license": "MIT" - }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "license": "MIT" - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "license": "BSD-3-Clause" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.5.tgz", + "integrity": "sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.5.tgz", + "integrity": "sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.5.tgz", + "integrity": "sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==", + "cpu": [ + "loong64" + ], + "dev": true, "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.5.tgz", + "integrity": "sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==", + "cpu": [ + "ppc64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tryer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", - "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", - "license": "MIT" - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "license": "Apache-2.0" - }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.5.tgz", + "integrity": "sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==", + "cpu": [ + "riscv64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.5.tgz", + "integrity": "sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==", + "cpu": [ + "riscv64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.5.tgz", + "integrity": "sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==", + "cpu": [ + "s390x" + ], + "dev": true, "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.5.tgz", + "integrity": "sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.5.tgz", + "integrity": "sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.5.tgz", + "integrity": "sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.5.tgz", + "integrity": "sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "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==", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.5.tgz", + "integrity": "sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==", + "cpu": [ + "ia32" + ], + "dev": true, "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==", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.5.tgz", + "integrity": "sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==", + "cpu": [ + "x64" + ], + "dev": true, "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" - } + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.5.tgz", + "integrity": "sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "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==", + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "license": "MIT", + "peer": true, "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" + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "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==", + "node_modules/@testing-library/react": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.1.tgz", + "integrity": "sha512-gr4KtAWqIOQoucWYD/f6ki+j5chXfcPc74Col/6poTyqTmn7zRmodWahWRCp8tYd+GMqBonw6hstNzqjbs6gjw==", "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" + "@babel/runtime": "^7.12.5" }, "engines": { - "node": ">= 0.4" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "license": "MIT", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" }, - "engines": { - "node": ">=4.2.0" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "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==", + "node_modules/@testing-library/user-event": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", + "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" + "@babel/runtime": "^7.12.5" }, "engines": { - "node": ">= 0.4" + "node": ">=10", + "npm": ">=6" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" } }, - "node_modules/underscore": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", - "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", - "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "license": "MIT" }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, "license": "MIT", "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "license": "MIT", - "engines": { - "node": ">=4" + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" } }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, "license": "MIT", "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==", - "license": "MIT" - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "license": "MIT", - "engines": { - "node": ">=4", - "yarn": "*" + "@babel/types": "^7.0.0" } }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, "license": "MIT", "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, "license": "MIT", "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" + "@babel/types": "^7.28.2" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, "license": "MIT" }, - "node_modules/util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "node_modules/@vitejs/plugin-react": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", + "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "dev": true, "license": "MIT", "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.53", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", - "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/v8-to-istanbul": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", - "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", - "license": "ISC", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" + "node": "^20.19.0 || >=22.12.0" }, - "engines": { - "node": ">=10.12.0" + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, - "node_modules/v8-to-istanbul/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "license": "MIT", - "dependencies": { - "browser-process-hrtime": "^1.0.0" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "license": "MIT", + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "license": "Apache-2.0", "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" + "dequal": "^2.0.3" } }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "node_modules/baseline-browser-mapping": { + "version": "2.9.9", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.9.tgz", + "integrity": "sha512-V8fbOCSeOFvlDj7LLChUcqbZrdKD9RU/VR260piF1790vT0mfLSwGc/Qzxv3IqiTukOpNtItePa0HBpMAj7MDg==", + "dev": true, "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" + "bin": { + "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", + "peer": true, "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" }, "engines": { - "node": ">=10.13.0" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "node_modules/caniuse-lite": { + "version": "1.0.30001760", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", + "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chart.js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", "license": "MIT", "dependencies": { - "minimalistic-assert": "^1.0.0" + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" } }, - "node_modules/web-vitals": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.4.tgz", - "integrity": "sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==", - "license": "Apache-2.0" + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "license": "BSD-2-Clause", + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", "engines": { - "node": ">=10.4" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/webpack": { - "version": "5.101.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", - "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", + "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": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.24.0", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", - "webpack-sources": "^3.3.3" - }, - "bin": { - "webpack": "bin/webpack.js" + "ms": "^2.1.3" }, "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "node": ">=6.0" }, "peerDependenciesMeta": { - "webpack-cli": { + "supports-color": { "optional": true } } }, - "node_modules/webpack-dev-middleware": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", - "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "license": "MIT", - "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" + "node": ">=6" } }, - "node_modules/webpack-dev-server": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", - "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.5", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.4", - "ws": "^8.13.0" - }, "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" + "esbuild": "bin/esbuild" }, "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" + "node": ">=18" }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-cli": { - "optional": true - } + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=12.0.0" }, "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "picomatch": "^3 || ^4" }, "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { + "picomatch": { "optional": true } } }, - "node_modules/webpack-manifest-plugin": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz", - "integrity": "sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "tapable": "^2.0.0", - "webpack-sources": "^2.2.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12.22.0" - }, - "peerDependencies": { - "webpack": "^4.44.2 || ^5.47.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/webpack-manifest-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" } }, - "node_modules/webpack-manifest-plugin/node_modules/webpack-sources": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", - "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, "license": "MIT", - "dependencies": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" + "bin": { + "jsesc": "bin/jsesc" }, "engines": { - "node": ">=10.13.0" + "node": ">=6" } }, - "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "bin": { + "json5": "lib/cli.js" }, "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/webpack/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" + "node": ">=6" } }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "license": "Apache-2.0", + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" + "yallist": "^3.0.2" } }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8.0" + "node_modules/lucide-react": { + "version": "0.542.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.542.0.tgz", + "integrity": "sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "license": "MIT", - "dependencies": { - "iconv-lite": "0.4.24" + "bin": { + "lz-string": "bin/bin.js" } }, - "node_modules/whatwg-encoding/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==", + "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/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">=0.10.0" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/whatwg-fetch": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", - "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, "license": "MIT" }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "license": "MIT" + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, - "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, + "peer": true, "engines": { - "node": ">=10" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "node": ">=12" }, - "engines": { - "node": ">= 8" + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "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==", + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "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" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^10 || ^12 || >=14" } }, - "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==", + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "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" + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, + "peer": true, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "node_modules/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", + "peer": true, "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" + "scheduler": "^0.27.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "react": "^19.2.3" } }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/workbox-background-sync": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.6.0.tgz", - "integrity": "sha512-jkf4ZdgOJxC9u2vztxLuPT/UjlH7m/nWRQ/MgGL0v8BJHoZdVGJd18Kck+a0e55wGXdqyHO+4IQTk0685g4MUw==", - "license": "MIT", - "dependencies": { - "idb": "^7.0.1", - "workbox-core": "6.6.0" - } - }, - "node_modules/workbox-broadcast-update": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.6.0.tgz", - "integrity": "sha512-nm+v6QmrIFaB/yokJmQ/93qIJ7n72NICxIwQwe5xsZiV2aI93MGGyEyzOzDPVz5THEr5rC3FJSsO3346cId64Q==", - "license": "MIT", - "dependencies": { - "workbox-core": "6.6.0" - } - }, - "node_modules/workbox-build": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.6.0.tgz", - "integrity": "sha512-Tjf+gBwOTuGyZwMz2Nk/B13Fuyeo0Q84W++bebbVsfr9iLkDSo6j6PST8tET9HYA58mlRXwlMGpyWO8ETJiXdQ==", + "node_modules/react-router": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.11.0.tgz", + "integrity": "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==", "license": "MIT", "dependencies": { - "@apideck/better-ajv-errors": "^0.3.1", - "@babel/core": "^7.11.1", - "@babel/preset-env": "^7.11.0", - "@babel/runtime": "^7.11.2", - "@rollup/plugin-babel": "^5.2.0", - "@rollup/plugin-node-resolve": "^11.2.1", - "@rollup/plugin-replace": "^2.4.1", - "@surma/rollup-plugin-off-main-thread": "^2.2.3", - "ajv": "^8.6.0", - "common-tags": "^1.8.0", - "fast-json-stable-stringify": "^2.1.0", - "fs-extra": "^9.0.1", - "glob": "^7.1.6", - "lodash": "^4.17.20", - "pretty-bytes": "^5.3.0", - "rollup": "^2.43.1", - "rollup-plugin-terser": "^7.0.0", - "source-map": "^0.8.0-beta.0", - "stringify-object": "^3.3.0", - "strip-comments": "^2.0.1", - "tempy": "^0.6.0", - "upath": "^1.2.0", - "workbox-background-sync": "6.6.0", - "workbox-broadcast-update": "6.6.0", - "workbox-cacheable-response": "6.6.0", - "workbox-core": "6.6.0", - "workbox-expiration": "6.6.0", - "workbox-google-analytics": "6.6.0", - "workbox-navigation-preload": "6.6.0", - "workbox-precaching": "6.6.0", - "workbox-range-requests": "6.6.0", - "workbox-recipes": "6.6.0", - "workbox-routing": "6.6.0", - "workbox-strategies": "6.6.0", - "workbox-streams": "6.6.0", - "workbox-sw": "6.6.0", - "workbox-window": "6.6.0" + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" }, "engines": { - "node": ">=10.0.0" + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } } }, - "node_modules/workbox-build/node_modules/@apideck/better-ajv-errors": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", - "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "node_modules/react-router-dom": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.11.0.tgz", + "integrity": "sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g==", "license": "MIT", "dependencies": { - "json-schema": "^0.4.0", - "jsonpointer": "^5.0.0", - "leven": "^3.1.0" + "react-router": "7.11.0" }, "engines": { - "node": ">=10" + "node": ">=20.0.0" }, "peerDependencies": { - "ajv": ">=8" + "react": ">=18", + "react-dom": ">=18" } }, - "node_modules/workbox-build/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "node_modules/rollup": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.5.tgz", + "integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==", + "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "@types/estree": "1.0.8" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/workbox-build/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "bin": { + "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=10" - } - }, - "node_modules/workbox-build/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/workbox-build/node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "deprecated": "The work that was done in this beta branch won't be included in future versions", - "license": "BSD-3-Clause", - "dependencies": { - "whatwg-url": "^7.0.0" + "node": ">=18.0.0", + "npm": ">=8.0.0" }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/workbox-build/node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "license": "MIT", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/workbox-build/node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "license": "BSD-2-Clause" - }, - "node_modules/workbox-build/node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "license": "MIT", - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "node_modules/workbox-cacheable-response": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.6.0.tgz", - "integrity": "sha512-JfhJUSQDwsF1Xv3EV1vWzSsCOZn4mQ38bWEBR3LdvOxSPgB65gAM6cS2CX8rkkKHRgiLrN7Wxoyu+TuH67kHrw==", - "deprecated": "workbox-background-sync@6.6.0", - "license": "MIT", - "dependencies": { - "workbox-core": "6.6.0" + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.5", + "@rollup/rollup-android-arm64": "4.53.5", + "@rollup/rollup-darwin-arm64": "4.53.5", + "@rollup/rollup-darwin-x64": "4.53.5", + "@rollup/rollup-freebsd-arm64": "4.53.5", + "@rollup/rollup-freebsd-x64": "4.53.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.5", + "@rollup/rollup-linux-arm-musleabihf": "4.53.5", + "@rollup/rollup-linux-arm64-gnu": "4.53.5", + "@rollup/rollup-linux-arm64-musl": "4.53.5", + "@rollup/rollup-linux-loong64-gnu": "4.53.5", + "@rollup/rollup-linux-ppc64-gnu": "4.53.5", + "@rollup/rollup-linux-riscv64-gnu": "4.53.5", + "@rollup/rollup-linux-riscv64-musl": "4.53.5", + "@rollup/rollup-linux-s390x-gnu": "4.53.5", + "@rollup/rollup-linux-x64-gnu": "4.53.5", + "@rollup/rollup-linux-x64-musl": "4.53.5", + "@rollup/rollup-openharmony-arm64": "4.53.5", + "@rollup/rollup-win32-arm64-msvc": "4.53.5", + "@rollup/rollup-win32-ia32-msvc": "4.53.5", + "@rollup/rollup-win32-x64-gnu": "4.53.5", + "@rollup/rollup-win32-x64-msvc": "4.53.5", + "fsevents": "~2.3.2" } }, - "node_modules/workbox-core": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.6.0.tgz", - "integrity": "sha512-GDtFRF7Yg3DD859PMbPAYPeJyg5gJYXuBQAC+wyrWuuXgpfoOrIQIvFRZnQ7+czTIQjIr1DhLEGFzZanAT/3bQ==", + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, - "node_modules/workbox-expiration": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.6.0.tgz", - "integrity": "sha512-baplYXcDHbe8vAo7GYvyAmlS4f6998Jff513L4XvlzAOxcl8F620O91guoJ5EOf5qeXG4cGdNZHkkVAPouFCpw==", - "license": "MIT", - "dependencies": { - "idb": "^7.0.1", - "workbox-core": "6.6.0" - } - }, - "node_modules/workbox-google-analytics": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.6.0.tgz", - "integrity": "sha512-p4DJa6OldXWd6M9zRl0H6vB9lkrmqYFkRQ2xEiNdBFp9U0LhsGO7hsBscVEyH9H2/3eZZt8c97NB2FD9U2NJ+Q==", - "deprecated": "It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained", - "license": "MIT", - "dependencies": { - "workbox-background-sync": "6.6.0", - "workbox-core": "6.6.0", - "workbox-routing": "6.6.0", - "workbox-strategies": "6.6.0" - } - }, - "node_modules/workbox-navigation-preload": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.6.0.tgz", - "integrity": "sha512-utNEWG+uOfXdaZmvhshrh7KzhDu/1iMHyQOV6Aqup8Mm78D286ugu5k9MFD9SzBT5TcwgwSORVvInaXWbvKz9Q==", - "license": "MIT", - "dependencies": { - "workbox-core": "6.6.0" - } - }, - "node_modules/workbox-precaching": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.6.0.tgz", - "integrity": "sha512-eYu/7MqtRZN1IDttl/UQcSZFkHP7dnvr/X3Vn6Iw6OsPMruQHiVjjomDFCNtd8k2RdjLs0xiz9nq+t3YVBcWPw==", - "license": "MIT", - "dependencies": { - "workbox-core": "6.6.0", - "workbox-routing": "6.6.0", - "workbox-strategies": "6.6.0" - } - }, - "node_modules/workbox-range-requests": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.6.0.tgz", - "integrity": "sha512-V3aICz5fLGq5DpSYEU8LxeXvsT//mRWzKrfBOIxzIdQnV/Wj7R+LyJVTczi4CQ4NwKhAaBVaSujI1cEjXW+hTw==", - "license": "MIT", - "dependencies": { - "workbox-core": "6.6.0" - } - }, - "node_modules/workbox-recipes": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.6.0.tgz", - "integrity": "sha512-TFi3kTgYw73t5tg73yPVqQC8QQjxJSeqjXRO4ouE/CeypmP2O/xqmB/ZFBBQazLTPxILUQ0b8aeh0IuxVn9a6A==", - "license": "MIT", - "dependencies": { - "workbox-cacheable-response": "6.6.0", - "workbox-core": "6.6.0", - "workbox-expiration": "6.6.0", - "workbox-precaching": "6.6.0", - "workbox-routing": "6.6.0", - "workbox-strategies": "6.6.0" - } - }, - "node_modules/workbox-routing": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.6.0.tgz", - "integrity": "sha512-x8gdN7VDBiLC03izAZRfU+WKUXJnbqt6PG9Uh0XuPRzJPpZGLKce/FkOX95dWHRpOHWLEq8RXzjW0O+POSkKvw==", - "license": "MIT", - "dependencies": { - "workbox-core": "6.6.0" - } - }, - "node_modules/workbox-strategies": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.6.0.tgz", - "integrity": "sha512-eC07XGuINAKUWDnZeIPdRdVja4JQtTuc35TZ8SwMb1ztjp7Ddq2CJ4yqLvWzFWGlYI7CG/YGqaETntTxBGdKgQ==", - "license": "MIT", - "dependencies": { - "workbox-core": "6.6.0" - } - }, - "node_modules/workbox-streams": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.6.0.tgz", - "integrity": "sha512-rfMJLVvwuED09CnH1RnIep7L9+mj4ufkTyDPVaXPKlhi9+0czCu+SJggWCIFbPpJaAZmp2iyVGLqS3RUmY3fxg==", - "license": "MIT", - "dependencies": { - "workbox-core": "6.6.0", - "workbox-routing": "6.6.0" + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/workbox-sw": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.6.0.tgz", - "integrity": "sha512-R2IkwDokbtHUE4Kus8pKO5+VkPHD2oqTgl+XJwh4zbF1HyjAbgNmK/FneZHVU7p03XUt9ICfuGDYISWG9qV/CQ==", + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "license": "MIT" }, - "node_modules/workbox-webpack-plugin": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.6.0.tgz", - "integrity": "sha512-xNZIZHalboZU66Wa7x1YkjIqEy1gTR+zPM+kjrYJzqN7iurYZBctBLISyScjhkJKYuRrZUP0iqViZTh8rS0+3A==", - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "^2.1.0", - "pretty-bytes": "^5.4.1", - "upath": "^1.2.0", - "webpack-sources": "^1.4.3", - "workbox-build": "6.6.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "webpack": "^4.4.0 || ^5.9.0" - } - }, - "node_modules/workbox-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/workbox-webpack-plugin/node_modules/webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "license": "MIT", - "dependencies": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - }, - "node_modules/workbox-window": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.6.0.tgz", - "integrity": "sha512-L4N9+vka17d16geaJXXRjENLFldvkWy7JyGxElRD0JvBxvFEd8LOhr+uXCcar/NzAmIBRv9EZ+M+Qr4mOoBITw==", - "license": "MIT", - "dependencies": { - "@types/trusted-types": "^2.0.2", - "workbox-core": "6.6.0" - } + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "license": "MIT" }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { - "node": ">=10" + "node": ">=12.0.0" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, - "engines": { - "node": ">=10" + "bin": { + "update-browserslist-db": "cli.js" }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "node_modules/vite": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, "engines": { - "node": ">=8.3.0" + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" }, "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { - "bufferutil": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { "optional": true }, - "utf-8-validate": { + "yaml": { "optional": true } } }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "license": "Apache-2.0" - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "license": "MIT" - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, "license": "ISC" - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } } } diff --git a/frontend/package.json b/frontend/package.json index 964a2701..d9e658df 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,7 +5,6 @@ "dependencies": { "@fontsource/league-spartan": "^5.2.7", "@testing-library/dom": "^10.4.1", - "@testing-library/jest-dom": "^6.8.0", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^13.5.0", "chart.js": "^4.5.0", @@ -13,20 +12,12 @@ "react": "^19.1.1", "react-dom": "^19.1.1", "react-router-dom": "^7.9.1", - "react-scripts": "5.0.1", - "web-vitals": "^2.1.4" + "tailwindcss": "^4.1.18" }, "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" - }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] + "start": "vite", + "build": "vite build", + "preview": "vite preview" }, "browserslist": { "production": [ @@ -41,8 +32,7 @@ ] }, "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^3.4.17" + "@vitejs/plugin-react": "^5.1.2", + "vite": "^7.3.0" } } diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js deleted file mode 100644 index 96bb01e7..00000000 --- a/frontend/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} \ No newline at end of file diff --git a/frontend/src/App.js b/frontend/src/App.jsx similarity index 100% rename from frontend/src/App.js rename to frontend/src/App.jsx diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js index 4f80e0a7..592b6653 100644 --- a/frontend/src/api/client.js +++ b/frontend/src/api/client.js @@ -1,4 +1,4 @@ -const API_BASE_URL = process.env.REACT_APP_API_URL; +const API_BASE_URL = import.meta.env.VITE_API_URL; if (!API_BASE_URL) { throw new Error('REACT_APP_API_URL environment variable must be set'); diff --git a/frontend/src/components/ComplianceChart.js b/frontend/src/components/ComplianceChart.jsx similarity index 100% rename from frontend/src/components/ComplianceChart.js rename to frontend/src/components/ComplianceChart.jsx diff --git a/frontend/src/components/Dropdown.js b/frontend/src/components/Dropdown.jsx similarity index 100% rename from frontend/src/components/Dropdown.js rename to frontend/src/components/Dropdown.jsx diff --git a/frontend/src/components/Sidebar.js b/frontend/src/components/Sidebar.jsx similarity index 100% rename from frontend/src/components/Sidebar.js rename to frontend/src/components/Sidebar.jsx diff --git a/frontend/src/context/AuthContext.js b/frontend/src/context/AuthContext.jsx similarity index 100% rename from frontend/src/context/AuthContext.js rename to frontend/src/context/AuthContext.jsx diff --git a/frontend/src/index.js b/frontend/src/main.jsx similarity index 94% rename from frontend/src/index.js rename to frontend/src/main.jsx index 0b319f53..02949254 100644 --- a/frontend/src/index.js +++ b/frontend/src/main.jsx @@ -1,6 +1,8 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import { BrowserRouter as Router } from 'react-router-dom'; +import { AuthProvider } from './context/AuthContext'; + // Tailwind + tokens + base (includes tokens.css & components.css) import './styles/global.css'; @@ -8,8 +10,7 @@ import './styles/global.css'; // Legacy app styles (keeps current look intact) import './index.css'; -import App from './App'; -import { AuthProvider } from './context/AuthContext'; +import App from './App.jsx'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( diff --git a/frontend/src/pages/Auth/LoginPage.js b/frontend/src/pages/Auth/LoginPage.jsx similarity index 100% rename from frontend/src/pages/Auth/LoginPage.js rename to frontend/src/pages/Auth/LoginPage.jsx diff --git a/frontend/src/pages/Auth/SignUpPage.js b/frontend/src/pages/Auth/SignUpPage.jsx similarity index 100% rename from frontend/src/pages/Auth/SignUpPage.js rename to frontend/src/pages/Auth/SignUpPage.jsx diff --git a/frontend/src/pages/Auth/components/BrandPanel.js b/frontend/src/pages/Auth/components/BrandPanel.jsx similarity index 100% rename from frontend/src/pages/Auth/components/BrandPanel.js rename to frontend/src/pages/Auth/components/BrandPanel.jsx diff --git a/frontend/src/pages/Auth/components/LoginHeader.js b/frontend/src/pages/Auth/components/LoginHeader.jsx similarity index 100% rename from frontend/src/pages/Auth/components/LoginHeader.js rename to frontend/src/pages/Auth/components/LoginHeader.jsx diff --git a/frontend/src/pages/Auth/components/SignInPanel.js b/frontend/src/pages/Auth/components/SignInPanel.jsx similarity index 100% rename from frontend/src/pages/Auth/components/SignInPanel.js rename to frontend/src/pages/Auth/components/SignInPanel.jsx diff --git a/frontend/src/pages/Connections/ConnectionsPage.js b/frontend/src/pages/Connections/ConnectionsPage.jsx similarity index 100% rename from frontend/src/pages/Connections/ConnectionsPage.js rename to frontend/src/pages/Connections/ConnectionsPage.jsx diff --git a/frontend/src/pages/Contact/ContactPage.js b/frontend/src/pages/Contact/ContactPage.jsx similarity index 100% rename from frontend/src/pages/Contact/ContactPage.js rename to frontend/src/pages/Contact/ContactPage.jsx diff --git a/frontend/src/pages/Contact/components/ContactForm.js b/frontend/src/pages/Contact/components/ContactForm.jsx similarity index 100% rename from frontend/src/pages/Contact/components/ContactForm.js rename to frontend/src/pages/Contact/components/ContactForm.jsx diff --git a/frontend/src/pages/Contact/components/ContactInfoGrid.js b/frontend/src/pages/Contact/components/ContactInfoGrid.jsx similarity index 100% rename from frontend/src/pages/Contact/components/ContactInfoGrid.js rename to frontend/src/pages/Contact/components/ContactInfoGrid.jsx diff --git a/frontend/src/pages/Contact/components/FAQSection.js b/frontend/src/pages/Contact/components/FAQSection.jsx similarity index 100% rename from frontend/src/pages/Contact/components/FAQSection.js rename to frontend/src/pages/Contact/components/FAQSection.jsx diff --git a/frontend/src/pages/Dashboard.js b/frontend/src/pages/Dashboard.jsx similarity index 100% rename from frontend/src/pages/Dashboard.js rename to frontend/src/pages/Dashboard.jsx diff --git a/frontend/src/pages/Evidence.js b/frontend/src/pages/Evidence.jsx similarity index 92% rename from frontend/src/pages/Evidence.js rename to frontend/src/pages/Evidence.jsx index d29ccc89..e03fc4ae 100644 --- a/frontend/src/pages/Evidence.js +++ b/frontend/src/pages/Evidence.jsx @@ -58,6 +58,46 @@ const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => { // Frontend UI state (React). // ------------------------------ // Available strategies to show in the dropdown (fetched from backend). + + const [observedSidebarWidth, setObservedSidebarWidth] = useState(sidebarWidth); + + useEffect(() => { + if (typeof window === 'undefined' || typeof ResizeObserver === 'undefined') { + setObservedSidebarWidth(sidebarWidth); + return; + } + const sidebarEl = document.querySelector('.sidebar'); + if (!sidebarEl) { + setObservedSidebarWidth(sidebarWidth); + return; + } + const observer = new ResizeObserver((entries) => { + const width = entries?.[0]?.contentRect?.width; + if (width && Math.abs(width - observedSidebarWidth) > 1) { + setObservedSidebarWidth(width); + } + }); + observer.observe(sidebarEl); + return () => observer.disconnect(); + }, [sidebarWidth, observedSidebarWidth]); + + const apiCandidates = useMemo(() => { + const roots = [ + import.meta.env.VITE_EVIDENCE_API_BASE, + import.meta.env.VITE_EVIDENCE_API, + import.meta.env.VITE_API_URL, + typeof window !== 'undefined' ? window.location.origin : null, + 'http://localhost:8000', + ] + .filter(Boolean) + .map((root) => root.replace(/\/+$/, '')); + + const urls = roots.map((root) => `${root}/v1/evidence`); + + return urls.filter((url, idx) => urls.indexOf(url) === idx); + }, []); + + const [apiBase, setApiBase] = useState(() => apiCandidates[0] || ''); const [strategies, setStrategies] = useState([]); // Currently selected strategy name (used in scan request). const [selectedStrategy, setSelectedStrategy] = useState(''); diff --git a/frontend/src/pages/Landing/AboutUs.js b/frontend/src/pages/Landing/AboutUs.jsx similarity index 100% rename from frontend/src/pages/Landing/AboutUs.js rename to frontend/src/pages/Landing/AboutUs.jsx diff --git a/frontend/src/pages/Landing/LandingPage.js b/frontend/src/pages/Landing/LandingPage.jsx similarity index 100% rename from frontend/src/pages/Landing/LandingPage.js rename to frontend/src/pages/Landing/LandingPage.jsx diff --git a/frontend/src/pages/Landing/components/BenefitsSection.js b/frontend/src/pages/Landing/components/BenefitsSection.jsx similarity index 100% rename from frontend/src/pages/Landing/components/BenefitsSection.js rename to frontend/src/pages/Landing/components/BenefitsSection.jsx diff --git a/frontend/src/pages/Landing/components/CTASection.js b/frontend/src/pages/Landing/components/CTASection.jsx similarity index 100% rename from frontend/src/pages/Landing/components/CTASection.js rename to frontend/src/pages/Landing/components/CTASection.jsx diff --git a/frontend/src/pages/Landing/components/FeaturesSection.js b/frontend/src/pages/Landing/components/FeaturesSection.jsx similarity index 100% rename from frontend/src/pages/Landing/components/FeaturesSection.js rename to frontend/src/pages/Landing/components/FeaturesSection.jsx diff --git a/frontend/src/pages/Landing/components/HeroSection.js b/frontend/src/pages/Landing/components/HeroSection.jsx similarity index 100% rename from frontend/src/pages/Landing/components/HeroSection.js rename to frontend/src/pages/Landing/components/HeroSection.jsx diff --git a/frontend/src/pages/Landing/components/LandingFooter.js b/frontend/src/pages/Landing/components/LandingFooter.jsx similarity index 100% rename from frontend/src/pages/Landing/components/LandingFooter.js rename to frontend/src/pages/Landing/components/LandingFooter.jsx diff --git a/frontend/src/pages/Landing/components/LandingHeader.js b/frontend/src/pages/Landing/components/LandingHeader.jsx similarity index 100% rename from frontend/src/pages/Landing/components/LandingHeader.js rename to frontend/src/pages/Landing/components/LandingHeader.jsx diff --git a/frontend/src/pages/Landing/components/StatsSection.js b/frontend/src/pages/Landing/components/StatsSection.jsx similarity index 100% rename from frontend/src/pages/Landing/components/StatsSection.js rename to frontend/src/pages/Landing/components/StatsSection.jsx diff --git a/frontend/src/pages/Scans/ScanDetailPage.js b/frontend/src/pages/Scans/ScanDetailPage.jsx similarity index 100% rename from frontend/src/pages/Scans/ScanDetailPage.js rename to frontend/src/pages/Scans/ScanDetailPage.jsx diff --git a/frontend/src/pages/Scans/ScansPage.js b/frontend/src/pages/Scans/ScansPage.jsx similarity index 100% rename from frontend/src/pages/Scans/ScansPage.js rename to frontend/src/pages/Scans/ScansPage.jsx diff --git a/frontend/src/pages/StyleGuide.js b/frontend/src/pages/StyleGuide.jsx similarity index 100% rename from frontend/src/pages/StyleGuide.js rename to frontend/src/pages/StyleGuide.jsx diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css index 301e492d..69c8108d 100644 --- a/frontend/src/styles/global.css +++ b/frontend/src/styles/global.css @@ -1,7 +1,4 @@ -/* src/styles/global.css */ -@tailwind base; -@tailwind components; -@tailwind utilities; +@import "tailwindcss"; /* order matters: tokens first, then component helpers */ @import "./tokens.css"; diff --git a/frontend/src/styles/tokens.css b/frontend/src/styles/tokens.css index 1be7114a..0c822d4a 100644 --- a/frontend/src/styles/tokens.css +++ b/frontend/src/styles/tokens.css @@ -27,6 +27,37 @@ --bp-sm: 600px; --bp-md: 900px; --bp-lg: 1200px; + + /* Surfaces */ + --surface-1: 16 18 22; + --surface-2: 22 25 30; + + /* Border */ + --border-subtle: 60 65 75; + + /* Text */ + --text-strong: 235 235 235; + --text-muted: 160 165 175; + + /* Accents */ + --accent-teal: 20 180 170; + --accent-navy: 15 30 80; + --accent-good: 40 180 60; + --accent-warn: 240 180 0; + --accent-bad: 220 40 40; + + /* Radius */ + --radius-2: 12px; + + /* Shadows */ + --shadow-1: 0 2px 4px rgba(0,0,0,0.06); + --shadow-2: 0 4px 8px rgba(0,0,0,0.1); + + /* Fonts */ + --font-header: "League Spartan", ui-sans-serif, system-ui, "Segoe UI", Inter, Arial; + --font-body: "Segoe UI", ui-sans-serif, system-ui, Inter, Arial; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + } /* Optional light theme */ @@ -38,4 +69,4 @@ --text-strong: 11 18 32; /* #0b1220 Rich Black */ --text-muted: 71 85 105; /* #475569 Payne's Grey*/ -} +} \ No newline at end of file diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 385c6c5c..5dddd510 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1,46 +1,4 @@ -const withOpacity = (varName) => ({ opacityValue }) => - opacityValue === undefined - ? `rgb(var(${varName}))` - : `rgb(var(${varName}) / ${opacityValue})`; - -module.exports = { - content: ["./src/**/*.{js,jsx,ts,tsx,html}"], - theme: { - extend: { - colors: { - surface: { - 1: withOpacity("--surface-1"), - 2: withOpacity("--surface-2"), - }, - border: { - subtle: withOpacity("--border-subtle"), - }, - text: { - strong: withOpacity("--text-strong"), - muted: withOpacity("--text-muted"), - }, - accent: { - teal: withOpacity("--accent-teal"), - navy: withOpacity("--accent-navy"), - good: withOpacity("--accent-good"), - warn: withOpacity("--accent-warn"), - bad: withOpacity("--accent-bad"), - }, - }, - borderRadius: { - card: "var(--radius-2)", // pick 2 as default card - }, - boxShadow: { - // optional: token shadows (not in use just yet) - "elev-1": "var(--shadow-1)", - "elev-2": "var(--shadow-2)", - }, - fontFamily: { - header: ['"League Spartan"', "ui-sans-serif", "system-ui", "Segoe UI", "Inter", "Arial"], - body: ["Segoe UI", "ui-sans-serif", "system-ui", "Inter", "Arial"], - mono: ["ui-monospace","SFMono-Regular","Menlo","Consolas","monospace"], - }, - }, - }, - plugins: [], -}; +export default { + content: ["./index.html", "./src/**/*.{js,jsx,ts,tsx,html}"], + }; + \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 00000000..8f335ca1 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + server: { + port: 3000, + }, +}); From b8c2e527643ea7d9e0d7174b3ec45f35d18e8231 Mon Sep 17 00:00:00 2001 From: David Hartley Date: Wed, 24 Dec 2025 16:18:05 +1100 Subject: [PATCH 060/111] Section 6 WIP --- .../v6.0.0/6.1.1_audit_disabled.rego | 56 +++++++++++ .../v6.0.0/6.1.2_mailbox_audit_actions.rego | 69 ++++++++++++++ .../v6.0.0/6.1.3_audit_bypass.rego | 50 ++++++++++ .../v6.0.0/6.2.1_mail_forwarding_blocked.rego | 54 +++++++++++ .../v6.0.0/6.2.2_transport_whitelist.rego | 58 ++++++++++++ .../v6.0.0/6.2.3_external_sender_tagging.rego | 66 +++++++++++++ .../v6.0.0/6.3.1_outlook_addins.rego | 50 ++++++++++ .../v6.0.0/6.5.1_modern_auth.rego | 56 +++++++++++ .../v6.0.0/6.5.2_mailtips.rego | 94 +++++++++++++++++++ .../v6.0.0/6.5.3_storage_providers.rego | 51 ++++++++++ .../v6.0.0/6.5.4_smtp_auth.rego | 56 +++++++++++ .../v6.0.0/6.5.5_direct_send.rego | 64 +++++++++++++ .../v6.0.0/metadata.json | 52 +++++----- engine/worker/tasks.py | 30 ++++-- 14 files changed, 771 insertions(+), 35 deletions(-) create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/6.1.1_audit_disabled.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/6.1.2_mailbox_audit_actions.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/6.1.3_audit_bypass.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/6.2.1_mail_forwarding_blocked.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/6.2.2_transport_whitelist.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/6.2.3_external_sender_tagging.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/6.3.1_outlook_addins.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.1_modern_auth.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.2_mailtips.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.3_storage_providers.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.4_smtp_auth.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.5_direct_send.rego diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.1.1_audit_disabled.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.1.1_audit_disabled.rego new file mode 100644 index 00000000..4cec9c49 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.1.1_audit_disabled.rego @@ -0,0 +1,56 @@ +# METADATA +# title: Ensure 'AuditDisabled' organizationally is set to 'False' +# description: | +# Mailbox auditing is enabled by default for all organizations. Ensure that +# organization-wide auditing has not been explicitly disabled, as this would +# prevent the capture of critical mailbox activities for security investigations. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-6.1.1 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: high +# service: Exchange +# requires_permissions: +# - Exchange.Manage + +package cis.microsoft_365_foundations.v6_0_0.control_6_1_1 + +default result := {"compliant": false, "message": "Evaluation failed"} + +result := output if { + audit_disabled := input.audit_disabled + + # Compliant when AuditDisabled is False (auditing is enabled) + compliant := audit_disabled == false + + output := { + "compliant": compliant, + "message": generate_message(audit_disabled), + "affected_resources": generate_affected_resources(compliant), + "details": { + "audit_disabled": audit_disabled + } + } +} + +generate_message(audit_disabled) := msg if { + audit_disabled == false + msg := "Organization-wide mailbox auditing is enabled (AuditDisabled = False)" +} + +generate_message(audit_disabled) := msg if { + audit_disabled == true + msg := "Organization-wide mailbox auditing is disabled (AuditDisabled = True)" +} + +generate_message(audit_disabled) := msg if { + audit_disabled == null + msg := "Unable to determine AuditDisabled status" +} + +generate_affected_resources(true) := [] +generate_affected_resources(false) := ["Organization mailbox auditing is disabled"] diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.1.2_mailbox_audit_actions.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.1.2_mailbox_audit_actions.rego new file mode 100644 index 00000000..cd4ad991 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.1.2_mailbox_audit_actions.rego @@ -0,0 +1,69 @@ +# METADATA +# title: Ensure mailbox audit actions are configured +# description: | +# Mailbox auditing should capture comprehensive audit actions for Admin, +# Delegate, and Owner operations. The default audit actions may not cover +# all necessary activities for security investigations and compliance. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-6.1.2 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: Exchange +# requires_permissions: +# - Exchange.Manage + +package cis.microsoft_365_foundations.v6_0_0.control_6_1_2 + +default result := {"compliant": false, "message": "Evaluation failed"} + +# Required audit actions per CIS benchmark +required_admin_actions := {"ApplyRecord", "Copy", "Create", "FolderBind", "HardDelete", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules"} +required_delegate_actions := {"ApplyRecord", "Create", "FolderBind", "HardDelete", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateFolderPermissions", "UpdateInboxRules"} +required_owner_actions := {"ApplyRecord", "Create", "HardDelete", "MailboxLogin", "Move", "MoveToDeletedItems", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules"} + +# Check if a mailbox has required audit actions +mailbox_has_required_actions(mailbox) if { + admin_actions := {a | some a in mailbox.AuditAdmin} + delegate_actions := {a | some a in mailbox.AuditDelegate} + owner_actions := {a | some a in mailbox.AuditOwner} + + count(required_admin_actions - admin_actions) == 0 + count(required_delegate_actions - delegate_actions) == 0 + count(required_owner_actions - owner_actions) == 0 +} + +result := output if { + mailboxes := input.mailboxes + total := count(mailboxes) + + # Find non-compliant mailboxes + non_compliant := [m.UserPrincipalName | some m in mailboxes; not mailbox_has_required_actions(m)] + + compliant := count(non_compliant) == 0 + + output := { + "compliant": compliant, + "message": generate_message(total, non_compliant), + "affected_resources": non_compliant, + "details": { + "total_mailboxes": total, + "compliant_mailboxes": total - count(non_compliant), + "non_compliant_mailboxes": count(non_compliant) + } + } +} + +generate_message(total, non_compliant) := msg if { + count(non_compliant) == 0 + msg := sprintf("All %d mailbox(es) have required audit actions configured", [total]) +} + +generate_message(total, non_compliant) := msg if { + count(non_compliant) > 0 + msg := sprintf("%d of %d mailbox(es) are missing required audit actions", [count(non_compliant), total]) +} diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.1.3_audit_bypass.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.1.3_audit_bypass.rego new file mode 100644 index 00000000..2e99d6a7 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.1.3_audit_bypass.rego @@ -0,0 +1,50 @@ +# METADATA +# title: Ensure 'AuditBypassEnabled' is not enabled on mailboxes +# description: | +# Mailbox audit bypass allows specified accounts to perform actions without +# generating audit entries. No mailboxes should have AuditBypassEnabled set +# to True, as this creates blind spots in security monitoring. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-6.1.3 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: Exchange +# requires_permissions: +# - Exchange.Manage + +package cis.microsoft_365_foundations.v6_0_0.control_6_1_3 + +default result := {"compliant": false, "message": "Evaluation failed"} + +result := output if { + accounts_with_bypass := input.accounts_with_bypass_enabled + bypass_count := input.bypass_count + + # Compliant when no accounts have audit bypass enabled + compliant := bypass_count == 0 + + output := { + "compliant": compliant, + "message": generate_message(bypass_count), + "affected_resources": [a.Name | some a in accounts_with_bypass], + "details": { + "accounts_with_bypass_enabled": bypass_count, + "bypassed_accounts": [a.Name | some a in accounts_with_bypass] + } + } +} + +generate_message(bypass_count) := msg if { + bypass_count == 0 + msg := "No accounts have mailbox audit bypass enabled" +} + +generate_message(bypass_count) := msg if { + bypass_count > 0 + msg := sprintf("%d account(s) have mailbox audit bypass enabled", [bypass_count]) +} diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.2.1_mail_forwarding_blocked.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.2.1_mail_forwarding_blocked.rego new file mode 100644 index 00000000..aa9c266b --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.2.1_mail_forwarding_blocked.rego @@ -0,0 +1,54 @@ +# METADATA +# title: Ensure all forms of mail forwarding are blocked and/or disabled +# description: | +# Transport rules that automatically forward or redirect mail to external +# recipients pose a significant data exfiltration risk. Ensure no transport +# rules exist that redirect or blind copy messages externally. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-6.2.1 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: high +# service: Exchange +# requires_permissions: +# - Exchange.Manage + +package cis.microsoft_365_foundations.v6_0_0.control_6_2_1 + +default result := {"compliant": false, "message": "Evaluation failed"} + +result := output if { + forwarding_rules := input.forwarding_rules + + # Filter to only enabled forwarding rules + enabled_forwarding_rules := [r | some r in forwarding_rules; r.state == "Enabled"] + + # Compliant when no enabled forwarding rules exist + compliant := count(enabled_forwarding_rules) == 0 + + output := { + "compliant": compliant, + "message": generate_message(enabled_forwarding_rules), + "affected_resources": [r.name | some r in enabled_forwarding_rules], + "details": { + "total_transport_rules": input.total_rules, + "forwarding_rules_count": count(forwarding_rules), + "enabled_forwarding_rules": count(enabled_forwarding_rules), + "forwarding_rules": enabled_forwarding_rules + } + } +} + +generate_message(enabled_forwarding_rules) := msg if { + count(enabled_forwarding_rules) == 0 + msg := "No enabled transport rules forward or redirect mail externally" +} + +generate_message(enabled_forwarding_rules) := msg if { + count(enabled_forwarding_rules) > 0 + msg := sprintf("%d enabled transport rule(s) forward or redirect mail externally", [count(enabled_forwarding_rules)]) +} diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.2.2_transport_whitelist.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.2.2_transport_whitelist.rego new file mode 100644 index 00000000..c65741b9 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.2.2_transport_whitelist.rego @@ -0,0 +1,58 @@ +# METADATA +# title: Ensure mail transport rules do not whitelist specific domains +# description: | +# Transport rules that whitelist specific domains by setting SCL to -1 or +# bypassing spam filtering based on sender domain create security risks. +# These rules can be exploited by attackers using spoofed domains. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-6.2.2 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: Exchange +# requires_permissions: +# - Exchange.Manage + +package cis.microsoft_365_foundations.v6_0_0.control_6_2_2 + +default result := {"compliant": false, "message": "Evaluation failed"} + +result := output if { + whitelist_rules := input.whitelist_rules + + # Filter to only enabled whitelist rules that set SCL to -1 + enabled_whitelist_rules := [r | + some r in whitelist_rules + r.state == "Enabled" + r.set_scl == -1 + ] + + # Compliant when no enabled whitelist rules exist + compliant := count(enabled_whitelist_rules) == 0 + + output := { + "compliant": compliant, + "message": generate_message(enabled_whitelist_rules), + "affected_resources": [r.name | some r in enabled_whitelist_rules], + "details": { + "total_transport_rules": input.total_rules, + "whitelist_rules_count": count(whitelist_rules), + "enabled_whitelist_rules": count(enabled_whitelist_rules), + "whitelist_rules": [{"name": r.name, "domains": r.sender_domain} | some r in enabled_whitelist_rules] + } + } +} + +generate_message(enabled_whitelist_rules) := msg if { + count(enabled_whitelist_rules) == 0 + msg := "No enabled transport rules whitelist specific domains" +} + +generate_message(enabled_whitelist_rules) := msg if { + count(enabled_whitelist_rules) > 0 + msg := sprintf("%d enabled transport rule(s) whitelist specific domains (SCL = -1)", [count(enabled_whitelist_rules)]) +} diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.2.3_external_sender_tagging.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.2.3_external_sender_tagging.rego new file mode 100644 index 00000000..3d7c856f --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.2.3_external_sender_tagging.rego @@ -0,0 +1,66 @@ +# METADATA +# title: Ensure email from external senders is identified +# description: | +# External sender identification helps users identify potentially malicious +# emails from outside the organization. This setting adds visual indicators +# to emails received from external sources. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-6.2.3 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: Exchange +# requires_permissions: +# - Exchange.Manage + +package cis.microsoft_365_foundations.v6_0_0.control_6_2_3 + +default result := {"compliant": false, "message": "Evaluation failed"} + +result := output if { + enabled := input.enabled + allowed_senders := input.allowed_senders + + # Compliant when external tagging is enabled + compliant := enabled == true + + output := { + "compliant": compliant, + "message": generate_message(enabled, allowed_senders), + "affected_resources": generate_affected_resources(compliant), + "details": { + "external_tagging_enabled": enabled, + "allowed_senders_count": count(allowed_senders), + "allowed_senders": allowed_senders + } + } +} + +generate_message(enabled, allowed_senders) := msg if { + enabled == true + count(allowed_senders) == 0 + msg := "External sender tagging is enabled with no exceptions" +} + +generate_message(enabled, allowed_senders) := msg if { + enabled == true + count(allowed_senders) > 0 + msg := sprintf("External sender tagging is enabled with %d exception(s)", [count(allowed_senders)]) +} + +generate_message(enabled, _) := msg if { + enabled == false + msg := "External sender tagging is disabled" +} + +generate_message(enabled, _) := msg if { + enabled == null + msg := "Unable to determine external sender tagging status" +} + +generate_affected_resources(true) := [] +generate_affected_resources(false) := ["External sender tagging is not enabled"] diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.3.1_outlook_addins.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.3.1_outlook_addins.rego new file mode 100644 index 00000000..3a0a6ee6 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.3.1_outlook_addins.rego @@ -0,0 +1,50 @@ +# METADATA +# title: Ensure users installing Outlook add-ins is not allowed +# description: | +# User installation of Outlook add-ins should be restricted to prevent +# potentially malicious add-ins from accessing mailbox data. Add-in +# installation should be centrally managed by administrators. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-6.3.1 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: Exchange +# requires_permissions: +# - Exchange.Manage + +package cis.microsoft_365_foundations.v6_0_0.control_6_3_1 + +default result := {"compliant": false, "message": "Evaluation failed"} + +result := output if { + policies_allowing_addins := input.policies_allowing_addin_install + + # Compliant when no policies allow add-in installation + compliant := count(policies_allowing_addins) == 0 + + output := { + "compliant": compliant, + "message": generate_message(policies_allowing_addins), + "affected_resources": [p.name | some p in policies_allowing_addins], + "details": { + "total_policies": input.total_policies, + "policies_allowing_addins": count(policies_allowing_addins), + "policy_details": policies_allowing_addins + } + } +} + +generate_message(policies_allowing_addins) := msg if { + count(policies_allowing_addins) == 0 + msg := "No role assignment policies allow user add-in installation" +} + +generate_message(policies_allowing_addins) := msg if { + count(policies_allowing_addins) > 0 + msg := sprintf("%d role assignment policy(ies) allow user add-in installation", [count(policies_allowing_addins)]) +} diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.1_modern_auth.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.1_modern_auth.rego new file mode 100644 index 00000000..2be77f66 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.1_modern_auth.rego @@ -0,0 +1,56 @@ +# METADATA +# title: Ensure modern authentication for Exchange Online is enabled +# description: | +# Modern authentication (OAuth 2.0) provides enhanced security features +# including MFA support and conditional access. OAuth2ClientProfileEnabled +# must be set to True for Exchange Online. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-6.5.1 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: high +# service: Exchange +# requires_permissions: +# - Exchange.Manage + +package cis.microsoft_365_foundations.v6_0_0.control_6_5_1 + +default result := {"compliant": false, "message": "Evaluation failed"} + +result := output if { + oauth_enabled := input.oauth_enabled + + # Compliant when OAuth authentication is enabled + compliant := oauth_enabled == true + + output := { + "compliant": compliant, + "message": generate_message(oauth_enabled), + "affected_resources": generate_affected_resources(compliant), + "details": { + "oauth2_client_profile_enabled": oauth_enabled + } + } +} + +generate_message(oauth_enabled) := msg if { + oauth_enabled == true + msg := "Modern authentication (OAuth 2.0) is enabled for Exchange Online" +} + +generate_message(oauth_enabled) := msg if { + oauth_enabled == false + msg := "Modern authentication (OAuth 2.0) is disabled for Exchange Online" +} + +generate_message(oauth_enabled) := msg if { + oauth_enabled == null + msg := "Unable to determine modern authentication status" +} + +generate_affected_resources(true) := [] +generate_affected_resources(false) := ["Modern authentication is disabled"] diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.2_mailtips.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.2_mailtips.rego new file mode 100644 index 00000000..cf844238 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.2_mailtips.rego @@ -0,0 +1,94 @@ +# METADATA +# title: Ensure MailTips are enabled for end users +# description: | +# MailTips provide informational messages to users as they compose emails, +# helping prevent accidental data disclosure and improving email hygiene. +# Key settings include tips for external recipients and large audiences. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-6.5.2 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: low +# service: Exchange +# requires_permissions: +# - Exchange.Manage + +package cis.microsoft_365_foundations.v6_0_0.control_6_5_2 + +default result := {"compliant": false, "message": "Evaluation failed"} + +# Helper to check if all required settings are properly configured +all_settings_compliant(all_tips, external_tips, group_metrics, threshold) if { + all_tips == true + external_tips == true + group_metrics == true + threshold != null + threshold > 0 +} + +result := output if { + config := input.organization_config + + all_tips_enabled := config.MailTipsAllTipsEnabled + external_tips_enabled := config.MailTipsExternalRecipientsTipsEnabled + group_metrics_enabled := config.MailTipsGroupMetricsEnabled + large_audience_threshold := config.MailTipsLargeAudienceThreshold + + # Compliant when all MailTips settings are properly configured + compliant := all_settings_compliant(all_tips_enabled, external_tips_enabled, group_metrics_enabled, large_audience_threshold) + + output := { + "compliant": compliant, + "message": generate_message(all_tips_enabled, external_tips_enabled, group_metrics_enabled), + "affected_resources": generate_affected_resources(all_tips_enabled, external_tips_enabled, group_metrics_enabled), + "details": { + "mail_tips_all_tips_enabled": all_tips_enabled, + "mail_tips_external_recipients_enabled": external_tips_enabled, + "mail_tips_group_metrics_enabled": group_metrics_enabled, + "mail_tips_large_audience_threshold": large_audience_threshold + } + } +} + +generate_message(all_tips, external_tips, group_metrics) := msg if { + all_tips == true + external_tips == true + group_metrics == true + msg := "MailTips are properly configured for end users" +} + +generate_message(all_tips, external_tips, group_metrics) := msg if { + not settings_all_true(all_tips, external_tips, group_metrics) + msg := "MailTips are not fully configured for end users" +} + +settings_all_true(all_tips, external_tips, group_metrics) if { + all_tips == true + external_tips == true + group_metrics == true +} + +generate_affected_resources(all_tips, external_tips, group_metrics) := resources if { + all_tips == true + external_tips == true + group_metrics == true + resources := [] +} + +generate_affected_resources(all_tips, external_tips, group_metrics) := resources if { + not settings_all_true(all_tips, external_tips, group_metrics) + resources := array.concat( + array.concat( + conditional_resource(all_tips != true, "MailTipsAllTipsEnabled is disabled"), + conditional_resource(external_tips != true, "MailTipsExternalRecipientsTipsEnabled is disabled") + ), + conditional_resource(group_metrics != true, "MailTipsGroupMetricsEnabled is disabled") + ) +} + +conditional_resource(true, msg) := [msg] +conditional_resource(false, _) := [] diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.3_storage_providers.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.3_storage_providers.rego new file mode 100644 index 00000000..aea99dd1 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.3_storage_providers.rego @@ -0,0 +1,51 @@ +# METADATA +# title: Ensure additional storage providers are restricted in Outlook on the web +# description: | +# Additional storage providers (Dropbox, Google Drive, Box, etc.) in Outlook +# on the web can lead to data leakage if not properly controlled. Restrict +# third-party storage providers to maintain data within corporate boundaries. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-6.5.3 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: Exchange +# requires_permissions: +# - Exchange.Manage + +package cis.microsoft_365_foundations.v6_0_0.control_6_5_3 + +default result := {"compliant": false, "message": "Evaluation failed"} + +result := output if { + policies_with_external_storage := input.policies_with_external_storage + total_policies := input.total_policies + + # Compliant when no policies allow external storage providers + compliant := count(policies_with_external_storage) == 0 + + output := { + "compliant": compliant, + "message": generate_message(policies_with_external_storage, total_policies), + "affected_resources": policies_with_external_storage, + "details": { + "total_owa_policies": total_policies, + "policies_with_external_storage": count(policies_with_external_storage), + "policy_names": policies_with_external_storage + } + } +} + +generate_message(policies_with_storage, total) := msg if { + count(policies_with_storage) == 0 + msg := sprintf("All %d OWA mailbox policy(ies) restrict additional storage providers", [total]) +} + +generate_message(policies_with_storage, total) := msg if { + count(policies_with_storage) > 0 + msg := sprintf("%d of %d OWA mailbox policy(ies) allow additional storage providers", [count(policies_with_storage), total]) +} diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.4_smtp_auth.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.4_smtp_auth.rego new file mode 100644 index 00000000..56db41f7 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.4_smtp_auth.rego @@ -0,0 +1,56 @@ +# METADATA +# title: Ensure SMTP AUTH is disabled +# description: | +# SMTP AUTH allows clients to submit email using basic authentication, +# which is vulnerable to credential theft attacks. Disable SMTP AUTH +# at the organization level to enforce modern authentication methods. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-6.5.4 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: Exchange +# requires_permissions: +# - Exchange.Manage + +package cis.microsoft_365_foundations.v6_0_0.control_6_5_4 + +default result := {"compliant": false, "message": "Evaluation failed"} + +result := output if { + smtp_auth_disabled := input.smtp_client_auth_disabled + + # Compliant when SMTP client authentication is disabled + compliant := smtp_auth_disabled == true + + output := { + "compliant": compliant, + "message": generate_message(smtp_auth_disabled), + "affected_resources": generate_affected_resources(compliant), + "details": { + "smtp_client_authentication_disabled": smtp_auth_disabled + } + } +} + +generate_message(smtp_auth_disabled) := msg if { + smtp_auth_disabled == true + msg := "SMTP AUTH is disabled at the organization level" +} + +generate_message(smtp_auth_disabled) := msg if { + smtp_auth_disabled == false + msg := "SMTP AUTH is enabled at the organization level" +} + +generate_message(smtp_auth_disabled) := msg if { + smtp_auth_disabled == null + msg := "Unable to determine SMTP AUTH status" +} + +generate_affected_resources(true) := [] +generate_affected_resources(false) := ["SMTP AUTH is enabled"] diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.5_direct_send.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.5_direct_send.rego new file mode 100644 index 00000000..c22dd62a --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.5_direct_send.rego @@ -0,0 +1,64 @@ +# METADATA +# title: Ensure Direct Send submissions are rejected +# description: | +# Direct Send allows anonymous SMTP connections to send email as the +# organization. This can be exploited for phishing and spoofing attacks. +# Configure transport settings to reject unauthenticated direct send. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-6.5.5 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: Exchange +# requires_permissions: +# - Exchange.Manage + +package cis.microsoft_365_foundations.v6_0_0.control_6_5_5 + +default result := {"compliant": false, "message": "Evaluation failed"} + +result := output if { + transport_config := input.transport_config + smtp_auth_disabled := input.smtp_client_authentication_disabled + + # Direct Send is blocked when SMTP client authentication is disabled + # Additional check: ExternalSubmissionEnabled should be False for full compliance + external_submission := transport_config.ExternalDelayDsnEnabled + + # Compliant when SMTP client authentication is disabled + compliant := smtp_auth_disabled == true + + output := { + "compliant": compliant, + "message": generate_message(smtp_auth_disabled), + "affected_resources": generate_affected_resources(compliant), + "details": { + "smtp_client_authentication_disabled": smtp_auth_disabled, + "transport_config": { + "external_delay_dsn_enabled": external_submission + } + } + } +} + +generate_message(smtp_auth_disabled) := msg if { + smtp_auth_disabled == true + msg := "Direct Send submissions are blocked (SMTP client auth disabled)" +} + +generate_message(smtp_auth_disabled) := msg if { + smtp_auth_disabled == false + msg := "Direct Send submissions are allowed (SMTP client auth enabled)" +} + +generate_message(smtp_auth_disabled) := msg if { + smtp_auth_disabled == null + msg := "Unable to determine Direct Send status" +} + +generate_affected_resources(true) := [] +generate_affected_resources(false) := ["Direct Send submissions are allowed"] diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json b/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json index 5cd0097e..b2217c95 100644 --- a/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json @@ -1306,9 +1306,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "exchange.organization.organization_config", - "policy_file": null, + "policy_file": "6.1.1_audit_disabled.rego", "requires_permissions": ["Exchange.Manage"], "notes": null }, @@ -1321,9 +1321,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "exchange.mailbox.mailbox_audit_actions", - "policy_file": null, + "policy_file": "6.1.2_mailbox_audit_actions.rego", "requires_permissions": ["Exchange.Manage"], "notes": null }, @@ -1336,9 +1336,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "exchange.mailbox.mailbox_audit", - "policy_file": null, + "policy_file": "6.1.3_audit_bypass.rego", "requires_permissions": ["Exchange.Manage"], "notes": null }, @@ -1351,9 +1351,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "exchange.transport.transport_rules", - "policy_file": null, + "policy_file": "6.2.1_mail_forwarding_blocked.rego", "requires_permissions": ["Exchange.Manage"], "notes": null }, @@ -1366,9 +1366,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "exchange.transport.transport_rules", - "policy_file": null, + "policy_file": "6.2.2_transport_whitelist.rego", "requires_permissions": ["Exchange.Manage"], "notes": null }, @@ -1381,9 +1381,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "exchange.transport.external_in_outlook", - "policy_file": null, + "policy_file": "6.2.3_external_sender_tagging.rego", "requires_permissions": ["Exchange.Manage"], "notes": null }, @@ -1396,9 +1396,9 @@ "level": "L2", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "exchange.mailbox.role_assignment_policy", - "policy_file": null, + "policy_file": "6.3.1_outlook_addins.rego", "requires_permissions": ["Exchange.Manage"], "notes": null }, @@ -1411,9 +1411,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "exchange.organization.organization_config", - "policy_file": null, + "policy_file": "6.5.1_modern_auth.rego", "requires_permissions": ["Exchange.Manage"], "notes": "Check OAuth2ClientProfileEnabled" }, @@ -1426,11 +1426,11 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "exchange.organization.organization_config", - "policy_file": null, + "policy_file": "6.5.2_mailtips.rego", "requires_permissions": ["Exchange.Manage"], - "notes": "Collector exists but control logic not defined" + "notes": null }, { "control_id": "6.5.3", @@ -1441,9 +1441,9 @@ "level": "L2", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "exchange.organization.owa_mailbox_policy", - "policy_file": null, + "policy_file": "6.5.3_storage_providers.rego", "requires_permissions": ["Exchange.Manage"], "notes": null }, @@ -1456,9 +1456,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "exchange.organization.organization_config", - "policy_file": null, + "policy_file": "6.5.4_smtp_auth.rego", "requires_permissions": ["Exchange.Manage"], "notes": null }, @@ -1471,11 +1471,11 @@ "level": "L2", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "exchange.organization.transport_config", - "policy_file": null, + "policy_file": "6.5.5_direct_send.rego", "requires_permissions": ["Exchange.Manage"], - "notes": "Collector exists but control logic not defined" + "notes": null }, { "control_id": "7.2.1", diff --git a/engine/worker/tasks.py b/engine/worker/tasks.py index 614de678..e6c3b909 100644 --- a/engine/worker/tasks.py +++ b/engine/worker/tasks.py @@ -325,18 +325,30 @@ async def _evaluate_control_async( # Import here to avoid circular imports from collectors.registry import get_collector from collectors.graph_client import GraphClient + from collectors.powershell_client import PowerShellClient from opa_client import opa_client - # Create Graph client with credentials - graph_client = GraphClient( - tenant_id=credentials["tenant_id"], - client_id=credentials["client_id"], - client_secret=credentials["client_secret"], - ) - - # Get collector and collect data + # Get collector collector = get_collector(collector_id) - collected_data = await collector.collect(graph_client) + + # Determine client type based on collector_id prefix + # Exchange and Compliance collectors require PowerShell + if collector_id.startswith(("exchange.", "compliance.")): + client = PowerShellClient( + tenant_id=credentials["tenant_id"], + client_id=credentials["client_id"], + client_secret=credentials["client_secret"], + ) + else: + # Entra and other collectors use Graph API + client = GraphClient( + tenant_id=credentials["tenant_id"], + client_id=credentials["client_id"], + client_secret=credentials["client_secret"], + ) + + # Collect data using the appropriate client + collected_data = await collector.collect(client) # Build OPA package path to match the Rego package declaration # Rego package: "cis.microsoft_365_foundations.v3_1_0.control_1_1_1" From b4427a76b94858df3d0534494c2b8d2ca512e499 Mon Sep 17 00:00:00 2001 From: SESH2002 Date: Tue, 30 Dec 2025 11:07:46 +1100 Subject: [PATCH 061/111] Update user instructions for regular backups evidences(ML1 AND ML2) --- security/evidence/USER_INSTRUCTIONS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/evidence/USER_INSTRUCTIONS.md b/security/evidence/USER_INSTRUCTIONS.md index 3a40bfe7..e4372f5c 100644 --- a/security/evidence/USER_INSTRUCTIONS.md +++ b/security/evidence/USER_INSTRUCTIONS.md @@ -1,4 +1,4 @@ -# Evidence preparation guide – Regular Backups (ML1 + ML2) +git # Evidence preparation guide – Regular Backups (ML1 + ML2) This guide explains how to prepare evidence files for the Regular Backups strategy so the Evidence Scanner can read and map them to the correct maturity-level tests. Files should contain clear text describing backup events, restore results, audit logs or policy settings. From 1bc3648dc7b8ebd83d7b88922ecb9dc3cd9b8054 Mon Sep 17 00:00:00 2001 From: Romil Bijarnia Date: Sat, 3 Jan 2026 15:58:19 +0530 Subject: [PATCH 062/111] added single sign on functionality to autoaudit, the sign in and the sign up pages both allow the users to sign in using google. --- backend-api/alembic/env.py | 1 + ...l567_expand_oauth_account_token_columns.py | 116 ++++++++++ backend-api/app/api/v1/auth.py | 215 ++++++++++++++++- backend-api/app/core/config.py | 9 + backend-api/app/core/users.py | 3 +- backend-api/app/db/init_db.py | 3 +- backend-api/app/models/__init__.py | 2 + backend-api/app/models/oauth_account.py | 35 +++ backend-api/app/models/user.py | 6 + backend-api/pyproject.toml | 3 + backend-api/uv.lock | 53 +++++ docker-compose.yml | 8 + docs/CONTRIBUTING.md | 7 +- docs/GETTING_STARTED.md | 75 ++---- frontend/src/App.js | 6 + frontend/src/api/client.js | 34 +-- frontend/src/components/Sidebar.js | 4 +- frontend/src/context/AuthContext.js | 26 ++- frontend/src/pages/AccountPage.js | 4 - frontend/src/pages/Auth/GoogleCallbackPage.js | 218 ++++++++++++++++++ frontend/src/pages/Auth/LoginPage.css | 27 +++ .../src/pages/Auth/components/SignInPanel.js | 51 ++-- .../pages/Auth/components/SignupFormPanel.js | 47 ++-- .../src/pages/Connections/ConnectionsPage.js | 2 - frontend/src/pages/Evidence.js | 7 +- .../Landing/components/BenefitsSection.js | 4 +- frontend/src/pages/SettingsPage.js | 4 +- 27 files changed, 841 insertions(+), 129 deletions(-) create mode 100644 backend-api/alembic/versions/h1i2j3k4l567_expand_oauth_account_token_columns.py create mode 100644 backend-api/app/models/oauth_account.py create mode 100644 frontend/src/pages/Auth/GoogleCallbackPage.js diff --git a/backend-api/alembic/env.py b/backend-api/alembic/env.py index 6259a048..e7ab362f 100644 --- a/backend-api/alembic/env.py +++ b/backend-api/alembic/env.py @@ -10,6 +10,7 @@ # Import models for autogenerate support from app.db.base import Base from app.models.user import User # noqa +from app.models.oauth_account import OAuthAccount # noqa from app.models.m365_connection import M365Connection # noqa from app.models.platform import Platform # noqa from app.models.compliance import Scan # noqa diff --git a/backend-api/alembic/versions/h1i2j3k4l567_expand_oauth_account_token_columns.py b/backend-api/alembic/versions/h1i2j3k4l567_expand_oauth_account_token_columns.py new file mode 100644 index 00000000..35b03065 --- /dev/null +++ b/backend-api/alembic/versions/h1i2j3k4l567_expand_oauth_account_token_columns.py @@ -0,0 +1,116 @@ +"""Add oauth_account table (squashed). + +This migration squashes the original two-step history: +- g7h8i9j0k123: create oauth_account table +- h1i2j3k4l567: expand token columns (access_token/refresh_token) to TEXT + +We create the table in its final schema (token columns as TEXT) to avoid +needing a follow-up migration. + +Revision ID: h1i2j3k4l567 +Revises: f6c7d8e9f012 +Create Date: 2025-12-27 +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "h1i2j3k4l567" +down_revision: Union[str, Sequence[str], None] = "f6c7d8e9f012" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None +# Support databases that may already be stamped at the original create-table revision. +replaces: Union[str, Sequence[str], None] = ("g7h8i9j0k123",) + + +def upgrade() -> None: + """Upgrade schema.""" + bind = op.get_bind() + inspector = sa.inspect(bind) + + existing_tables = set(inspector.get_table_names()) + if "oauth_account" not in existing_tables: + op.create_table( + "oauth_account", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("oauth_name", sa.String(length=100), nullable=False), + # Use TEXT for provider tokens (can exceed 1024 chars). + sa.Column("access_token", sa.Text(), nullable=False), + sa.Column("expires_at", sa.Integer(), nullable=True), + sa.Column("refresh_token", sa.Text(), nullable=True), + sa.Column("account_id", sa.String(length=320), nullable=False), + sa.Column("account_email", sa.String(length=320), nullable=False), + sa.Column("user_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(["user_id"], ["user.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint( + "oauth_name", + "account_id", + name="uq_oauth_account_oauth_name_account_id", + ), + ) + + op.create_index( + op.f("ix_oauth_account_oauth_name"), + "oauth_account", + ["oauth_name"], + ) + op.create_index( + op.f("ix_oauth_account_account_id"), + "oauth_account", + ["account_id"], + ) + op.create_index( + op.f("ix_oauth_account_user_id"), + "oauth_account", + ["user_id"], + ) + return + + # If the table already exists (e.g., database was created with the old migration), + # ensure token columns are large enough. + op.alter_column( + "oauth_account", + "access_token", + existing_type=sa.String(length=1024), + type_=sa.Text(), + existing_nullable=False, + ) + op.alter_column( + "oauth_account", + "refresh_token", + existing_type=sa.String(length=1024), + type_=sa.Text(), + existing_nullable=True, + ) + + +def downgrade() -> None: + """Downgrade schema.""" + bind = op.get_bind() + inspector = sa.inspect(bind) + + if "oauth_account" not in set(inspector.get_table_names()): + return + + # Drop indexes first (naming convention uses op.f()). + op.drop_index(op.f("ix_oauth_account_user_id"), table_name="oauth_account") + op.drop_index(op.f("ix_oauth_account_account_id"), table_name="oauth_account") + op.drop_index(op.f("ix_oauth_account_oauth_name"), table_name="oauth_account") + op.drop_table("oauth_account") + + + + + + + + + + + + + diff --git a/backend-api/app/api/v1/auth.py b/backend-api/app/api/v1/auth.py index c32ad392..fa39f0d5 100644 --- a/backend-api/app/api/v1/auth.py +++ b/backend-api/app/api/v1/auth.py @@ -1,6 +1,14 @@ -from fastapi import APIRouter, Depends +import secrets +from urllib.parse import urlencode + +import httpx +from fastapi import APIRouter, Depends, HTTPException, Request, status from fastapi_users import exceptions -from app.core.users import fastapi_users, auth_backend +from fastapi.responses import RedirectResponse +from httpx_oauth.clients.google import GoogleOAuth2 + +from app.core.config import get_settings +from app.core.users import auth_backend, fastapi_users, get_jwt_strategy, get_user_manager from app.schemas.user import UserRead, UserCreate, UserRegister, UserUpdate from app.core.auth import get_current_user from app.models.user import User @@ -76,3 +84,206 @@ async def change_password( # Include users router router.include_router(users_router) + + +GOOGLE_OAUTH_STATE_COOKIE = "google_oauth_state" + + +def _google_redirect_uri() -> str: + settings = get_settings() + return f"{settings.BACKEND_PUBLIC_URL}{settings.API_PREFIX}/auth/google/callback" + + +def _frontend_google_callback_url(fragment_params: dict[str, str]) -> str: + settings = get_settings() + base = settings.FRONTEND_URL.rstrip("/") + fragment = urlencode(fragment_params) + return f"{base}/auth/google/callback#{fragment}" + + +def _google_oauth_client() -> GoogleOAuth2: + settings = get_settings() + if not settings.GOOGLE_OAUTH_CLIENT_ID or not settings.GOOGLE_OAUTH_CLIENT_SECRET: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Google OAuth is not configured (missing GOOGLE_OAUTH_CLIENT_ID/SECRET).", + ) + return GoogleOAuth2( + settings.GOOGLE_OAUTH_CLIENT_ID, + settings.GOOGLE_OAUTH_CLIENT_SECRET, + scopes=["openid", "email", "profile"], + name="google", + ) + + +@router.get("/google/authorize") +async def google_authorize() -> RedirectResponse: + """Start Google OAuth (redirect to Google authorize URL).""" + settings = get_settings() + state = secrets.token_urlsafe(32) + + try: + client = _google_oauth_client() + except HTTPException as exc: + return RedirectResponse( + _frontend_google_callback_url( + { + "error": "oauth_not_configured", + "error_description": str(exc.detail), + } + ), + status_code=status.HTTP_302_FOUND, + ) + + authorize_url = await client.get_authorization_url( + redirect_uri=_google_redirect_uri(), + state=state, + scope=["openid", "email", "profile"], + # Don't force prompt=select_account; it adds an extra step and slows SSO. + extras_params=None, + ) + + response = RedirectResponse(authorize_url, status_code=status.HTTP_302_FOUND) + response.set_cookie( + key=GOOGLE_OAUTH_STATE_COOKIE, + value=state, + max_age=600, + httponly=True, + secure=settings.BACKEND_PUBLIC_URL.startswith("https://"), + samesite="lax", + path=f"{settings.API_PREFIX}/auth/google/callback", + ) + return response + + +@router.get("/google/callback") +async def google_callback( + request: Request, + code: str | None = None, + state: str | None = None, + user_manager=Depends(get_user_manager), +) -> RedirectResponse: + """Google OAuth callback: exchange code, link/create user, mint AutoAudit JWT, redirect to FE.""" + settings = get_settings() + + cookie_state = request.cookies.get(GOOGLE_OAUTH_STATE_COOKIE) + if not state or not cookie_state or state != cookie_state: + return RedirectResponse( + _frontend_google_callback_url( + { + "error": "invalid_state", + "error_description": "Invalid OAuth state. Please try again.", + } + ), + status_code=status.HTTP_302_FOUND, + ) + + if not code: + return RedirectResponse( + _frontend_google_callback_url( + { + "error": "missing_code", + "error_description": "Google did not return an authorization code.", + } + ), + status_code=status.HTTP_302_FOUND, + ) + + client = _google_oauth_client() + + try: + token = await client.get_access_token(code, redirect_uri=_google_redirect_uri()) + google_access_token = token["access_token"] + except Exception: + return RedirectResponse( + _frontend_google_callback_url( + { + "error": "token_exchange_failed", + "error_description": "Failed to exchange authorization code for tokens.", + } + ), + status_code=status.HTTP_302_FOUND, + ) + + # Fetch OIDC userinfo for email + verification + stable subject identifier (sub). + try: + async with httpx.AsyncClient(timeout=10.0) as http: + resp = await http.get( + "https://openidconnect.googleapis.com/v1/userinfo", + headers={"Authorization": f"Bearer {google_access_token}"}, + ) + resp.raise_for_status() + profile = resp.json() + except Exception: + return RedirectResponse( + _frontend_google_callback_url( + { + "error": "userinfo_failed", + "error_description": "Failed to fetch Google user profile.", + } + ), + status_code=status.HTTP_302_FOUND, + ) + + email = profile.get("email") + email_verified = profile.get("email_verified") + sub = profile.get("sub") + + if not email or not sub: + return RedirectResponse( + _frontend_google_callback_url( + { + "error": "invalid_profile", + "error_description": "Google profile is missing required fields.", + } + ), + status_code=status.HTTP_302_FOUND, + ) + + # Link-by-email requires the email to be verified to avoid account takeover. + if email_verified is not True: + return RedirectResponse( + _frontend_google_callback_url( + { + "error": "email_not_verified", + "error_description": "Google account email is not verified.", + } + ), + status_code=status.HTTP_302_FOUND, + ) + + try: + user = await user_manager.oauth_callback( + oauth_name="google", + access_token=google_access_token, + account_id=sub, + account_email=email, + expires_at=token.get("expires_at"), + refresh_token=token.get("refresh_token"), + request=request, + associate_by_email=True, + is_verified_by_default=True, + ) + except Exception: + return RedirectResponse( + _frontend_google_callback_url( + { + "error": "user_link_failed", + "error_description": "Failed to link Google account to user.", + } + ), + status_code=status.HTTP_302_FOUND, + ) + + # fastapi-users JWTStrategy.write_token is async in the version used by the backend container. + autoaudit_token = await get_jwt_strategy().write_token(user) + redirect_url = _frontend_google_callback_url( + {"access_token": autoaudit_token, "token_type": "bearer"} + ) + + response = RedirectResponse(redirect_url, status_code=status.HTTP_302_FOUND) + response.delete_cookie( + GOOGLE_OAUTH_STATE_COOKIE, + path=f"{settings.API_PREFIX}/auth/google/callback", + ) + return response diff --git a/backend-api/app/core/config.py b/backend-api/app/core/config.py index 7afca264..8a87af82 100644 --- a/backend-api/app/core/config.py +++ b/backend-api/app/core/config.py @@ -15,6 +15,15 @@ class Settings(BaseSettings): ALGORITHM: str = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 + # Public URLs (used for OAuth redirects) + # These must be the externally reachable URLs (e.g. localhost from the browser). + BACKEND_PUBLIC_URL: str = "http://localhost:8000" + FRONTEND_URL: str = "http://localhost:3000" + + # Google OAuth (SSO) + GOOGLE_OAUTH_CLIENT_ID: str = "" + GOOGLE_OAUTH_CLIENT_SECRET: str = "" + # Redis (for Celery broker) REDIS_URL: str = "redis://localhost:6379" diff --git a/backend-api/app/core/users.py b/backend-api/app/core/users.py index 3e3e1418..d8440471 100644 --- a/backend-api/app/core/users.py +++ b/backend-api/app/core/users.py @@ -24,6 +24,7 @@ from app.core.config import get_settings from app.db.session import get_async_session from app.models.user import User +from app.models.oauth_account import OAuthAccount settings = get_settings() @@ -53,7 +54,7 @@ async def on_after_request_verify( async def get_user_db(session: AsyncSession = Depends(get_async_session)): """Dependency for getting the user database.""" - yield SQLAlchemyUserDatabase(session, User) + yield SQLAlchemyUserDatabase(session, User, OAuthAccount) async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)): diff --git a/backend-api/app/db/init_db.py b/backend-api/app/db/init_db.py index fcb9ef44..8c21bbcd 100644 --- a/backend-api/app/db/init_db.py +++ b/backend-api/app/db/init_db.py @@ -31,7 +31,8 @@ async def init_db(): result = await session.execute( select(User).where(User.email == admin_email) ) - existing_user = result.scalar_one_or_none() + # Be resilient to relationship eager-loads that can duplicate rows. + existing_user = result.unique().scalar_one_or_none() created = False if existing_user: diff --git a/backend-api/app/models/__init__.py b/backend-api/app/models/__init__.py index 511dbc00..9e6e8e80 100644 --- a/backend-api/app/models/__init__.py +++ b/backend-api/app/models/__init__.py @@ -1,6 +1,7 @@ """Database models for the AutoAudit backend API.""" from app.models.user import User, Role +from app.models.oauth_account import OAuthAccount from app.models.m365_connection import M365Connection from app.models.azure_connection import AzureConnection from app.models.gcp_connection import GCPConnection @@ -13,6 +14,7 @@ __all__ = [ "User", "Role", + "OAuthAccount", "M365Connection", "AzureConnection", "GCPConnection", diff --git a/backend-api/app/models/oauth_account.py b/backend-api/app/models/oauth_account.py new file mode 100644 index 00000000..c700e6b5 --- /dev/null +++ b/backend-api/app/models/oauth_account.py @@ -0,0 +1,35 @@ +from typing import TYPE_CHECKING + +from fastapi_users.db import SQLAlchemyBaseOAuthAccountTable +from sqlalchemy import ForeignKey, UniqueConstraint +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from app.db.base import Base + +if TYPE_CHECKING: + from app.models.user import User + + +class OAuthAccount(SQLAlchemyBaseOAuthAccountTable[int], Base): + """OAuth account linked to a User (e.g., Google identity).""" + + __tablename__ = "oauth_account" + __table_args__ = ( + UniqueConstraint( + "oauth_name", + "account_id", + name="uq_oauth_account_oauth_name_account_id", + ), + ) + + id: Mapped[int] = mapped_column(primary_key=True) + user_id: Mapped[int] = mapped_column( + ForeignKey("user.id", ondelete="CASCADE"), + nullable=False, + index=True, + ) + + user: Mapped["User"] = relationship(back_populates="oauth_accounts") + + + diff --git a/backend-api/app/models/user.py b/backend-api/app/models/user.py index 0bdc4cba..d409ba64 100644 --- a/backend-api/app/models/user.py +++ b/backend-api/app/models/user.py @@ -11,6 +11,7 @@ from app.models.compliance import Scan from app.models.evidence_validation import EvidenceValidation from app.models.m365_connection import M365Connection + from app.models.oauth_account import OAuthAccount class Role(str, Enum): @@ -42,6 +43,11 @@ class User(SQLAlchemyBaseUserTable[int], Base): # - is_verified: bool # Relationships + oauth_accounts: Mapped[list["OAuthAccount"]] = relationship( + back_populates="user", + cascade="all, delete-orphan", + lazy="selectin", + ) m365_connections: Mapped[list["M365Connection"]] = relationship( back_populates="user" ) diff --git a/backend-api/pyproject.toml b/backend-api/pyproject.toml index c2f4f1f0..8e805c0d 100644 --- a/backend-api/pyproject.toml +++ b/backend-api/pyproject.toml @@ -14,6 +14,9 @@ dependencies = [ "sqlalchemy>=2.0.0", "asyncpg>=0.29.0", "alembic>=1.13.0", + # OAuth helpers (Google SSO) + "httpx>=0.27.0", + "httpx-oauth>=0.15.0", # Multipart upload support for evidence/file endpoints "python-multipart>=0.0.12", # Encryption for credentials at rest diff --git a/backend-api/uv.lock b/backend-api/uv.lock index c92f1ea7..9494beaf 100644 --- a/backend-api/uv.lock +++ b/backend-api/uv.lock @@ -189,6 +189,8 @@ dependencies = [ { name = "cryptography" }, { name = "fastapi" }, { name = "fastapi-users", extra = ["sqlalchemy"] }, + { name = "httpx" }, + { name = "httpx-oauth" }, { name = "pydantic" }, { name = "pydantic-settings" }, { name = "python-dotenv" }, @@ -219,6 +221,8 @@ requires-dist = [ { name = "fastapi", specifier = ">=0.116.1" }, { name = "fastapi-users", extras = ["sqlalchemy"], specifier = ">=13.0.0" }, { name = "fpdf2", marker = "extra == 'evidence'", specifier = ">=2.8.1" }, + { name = "httpx", specifier = ">=0.27.0" }, + { name = "httpx-oauth", specifier = ">=0.15.0" }, { name = "opencv-python", marker = "extra == 'evidence'", specifier = ">=4.10.0.84" }, { name = "pillow", marker = "extra == 'evidence'", specifier = ">=10.4.0" }, { name = "pydantic", specifier = ">=2.11.7" }, @@ -327,6 +331,15 @@ redis = [ { name = "kombu", extra = ["redis"] }, ] +[[package]] +name = "certifi" +version = "2025.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, +] + [[package]] name = "cffi" version = "2.0.0" @@ -779,6 +792,46 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-oauth" +version = "0.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/07/db4ad128da3926be22eec586aa87dafd8840c9eb03fe88505fbed016b5c6/httpx_oauth-0.16.1.tar.gz", hash = "sha256:7402f061f860abc092ea4f5c90acfc576a40bbb79633c1d2920f1ca282c296ee", size = 44148, upload-time = "2024-12-20T07:23:02.589Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/4b/2b81e876abf77b4af3372aff731f4f6722840ebc7dcfd85778eaba271733/httpx_oauth-0.16.1-py3-none-any.whl", hash = "sha256:2fcad82f80f28d0473a0fc4b4eda223dc952050af7e3a8c8781342d850f09fb5", size = 38056, upload-time = "2024-12-20T07:23:00.394Z" }, +] + [[package]] name = "idna" version = "3.10" diff --git a/docker-compose.yml b/docker-compose.yml index f32c0969..8bc1495d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -81,6 +81,14 @@ services: # Required: JWT signing key (CHANGE IN PRODUCTION) - SECRET_KEY=dev-secret-key-change-in-production + # Public URLs (used for OAuth redirects) + - BACKEND_PUBLIC_URL=http://localhost:8000 + - FRONTEND_URL=http://localhost:3000 + + # Google OAuth (SSO) - set these after creating credentials in Google Cloud Console + - GOOGLE_OAUTH_CLIENT_ID=237734019606-8lft9r71d02ljcegsq4d6huglh8ke151.apps.googleusercontent.com + - GOOGLE_OAUTH_CLIENT_SECRET=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 + # Required: Redis URL for Celery task queue - REDIS_URL=redis://redis:6379 diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 4aa78f7a..bb4dc6f3 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -64,7 +64,8 @@ AutoAudit is a compliance automation platform for cloud environments. It collect **Key directories**: - `src/components/` - Reusable UI components - `src/pages/` - Route-level page components -- `src/services/` - API client and data fetching +- `src/api/` - API client and data fetching +- `src/context/` - Shared app state (e.g. auth) **Note**: The frontend currently uses mock data in some areas. Connecting it to the live backend API is ongoing work. @@ -106,7 +107,7 @@ When you finish implementing a data collector: 1. **Register the collector** in `engine/collectors/registry.py`: ```python - from engine.collectors.entra.my_domain.my_collector import MyCollector + from collectors.entra.my_domain.my_collector import MyCollector DATA_COLLECTORS: dict[str, type[BaseDataCollector]] = { # ... existing collectors ... @@ -130,7 +131,7 @@ When you finish implementing a data collector: - Add example JSON output - Note what the OPA policy can evaluate - File: `docs/engine/cis-m365-v6-collectors-analysis.md` + File: `docs/engine/policies/cis/microsoft-365-foundations/vX.X.X/controls.md` 5. **Test your collector** against a live M365 tenant (see Testing Your Collector below) diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 2379b8f5..1ea22c5e 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -27,7 +27,7 @@ cd AutoAudit The fastest way to get everything running is with Docker Compose. This will start the full stack including the database, Redis, OPA, and all application services. ```bash -docker compose --profile all up -d +docker compose --profile all up --build -d ``` Once the containers are running: @@ -42,56 +42,12 @@ The backend automatically runs database migrations and seeds a default admin use - Email: `admin@example.com` - Password: `admin` -## Development Profiles +## SSO Test Accounts (Development Only) -Docker Compose is configured with profiles to support different development workflows. This lets you run some services in Docker while developing others locally. +These credentials are for local development/testing only. -### Infrastructure Only (default) - -Starts the infrastructure services. Use this when you want to run both the frontend and backend locally. - -```bash -docker compose up -d -``` - -Services started: -- PostgreSQL on port 5432 -- Redis on port 6379 -- OPA on port 8181 - -### Frontend Development - -If you're working on the frontend and want the backend running in Docker: - -```bash -docker compose --profile frontend-dev up -d -cd frontend -npm install -npm start -``` - -This starts the backend-api in Docker (port 8000), and you run the frontend locally (port 3000). - -### Backend Development - -If you're working on the backend and want the frontend running in Docker: - -```bash -docker compose --profile backend-dev up -d -cd backend-api -uv sync -uv run uvicorn app.main:app --reload --port 8000 -``` - -This starts the frontend in Docker (port 3000), and you run the backend locally (port 8000). - -### Full Stack in Docker - -For testing or demos, run everything in containers: - -```bash -docker compose --profile all up -d -``` +- Google SSO test user email: `autoauditdev@gmail.com` +- Google SSO test user password: `autoauditlocal123#` ## Module-Specific Setup @@ -107,6 +63,12 @@ cd backend-api # Install dependencies uv sync +# Run database migrations +uv run alembic upgrade head + +# Seed default admin user (optional, dev only) +uv run python -m app.db.init_db + # Start the development server with hot reload uv run uvicorn app.main:app --reload --port 8000 ``` @@ -133,22 +95,13 @@ The app will open at http://localhost:3000. ### Engine -The engine handles compliance scanning and policy evaluation. It runs as a Celery worker that connects to PostgreSQL, Redis, and OPA, so you need the infrastructure services running first. +The engine runs as the `worker` (Celery) service when you start the full stack with Docker Compose: ```bash -# Start infrastructure services (from the repo root) -docker compose up -d - -cd engine - -# Install dependencies -uv sync - -# Run the Celery worker -uv run celery -A worker.celery_app worker --loglevel=info +docker compose --profile all up --build -d ``` -The worker logs are output directly to your terminal. Use `--loglevel=debug` for more verbose output when troubleshooting. The worker will display task execution status, errors, and processing details as scans are triggered from the backend API. +The worker logs are output directly to the Docker logs. You can view them with `docker compose logs -f worker`. ## Verifying Your Setup diff --git a/frontend/src/App.js b/frontend/src/App.js index bd250249..06f9cf55 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -18,6 +18,7 @@ import AboutUs from './pages/Landing/AboutUs'; import ContactPage from './pages/Contact/ContactPage'; import LoginPage from './pages/Auth/LoginPage'; import SignUpPage from './pages/Auth/SignUpPage'; +import GoogleCallbackPage from './pages/Auth/GoogleCallbackPage'; // Auth Context import { useAuth } from './context/AuthContext'; @@ -163,6 +164,11 @@ function App() { /> } /> + + } + /> ({ detail: 'Login failed' })); - throw new APIError(error.detail || 'Invalid credentials', response.status, error); - } + if (!response.ok) { + const error = await response.json().catch(() => ({ detail: 'Login failed' })); + throw new APIError(error.detail || 'Invalid credentials', response.status, error); + } - return response.json(); + return response.json(); + } catch (error) { + throw error; + } } export async function register(email, password) { diff --git a/frontend/src/components/Sidebar.js b/frontend/src/components/Sidebar.js index 7cdbb1dc..4cf80258 100644 --- a/frontend/src/components/Sidebar.js +++ b/frontend/src/components/Sidebar.js @@ -10,7 +10,7 @@ import { FileSearch, ShieldCheck, FileText, - Settings2, + Settings, User, Menu, ArrowLeft, @@ -182,7 +182,7 @@ const Sidebar = ({ onWidthChange = () => {}, isDarkMode = true }) => { handleNavClick("settings", "/settings")} diff --git a/frontend/src/context/AuthContext.js b/frontend/src/context/AuthContext.js index a0e87567..e42766f9 100644 --- a/frontend/src/context/AuthContext.js +++ b/frontend/src/context/AuthContext.js @@ -1,4 +1,4 @@ -import React, { createContext, useContext, useState, useEffect } from "react"; +import React, { createContext, useContext, useEffect, useRef, useState } from "react"; import { login as apiLogin, getCurrentUser, APIError } from "../api/client"; const AuthContext = createContext(null); @@ -53,6 +53,7 @@ export function AuthProvider({ children }) { const [user, setUser] = useState(() => getStoredUser()); const [token, setToken] = useState(() => getStoredToken()); const [isLoading, setIsLoading] = useState(true); + const skipNextValidationRef = useRef(false); const isAuthenticated = !!token && !!user; @@ -64,6 +65,13 @@ export function AuthProvider({ children }) { return; } + // Avoid a duplicate /users/me call right after a successful login. + if (skipNextValidationRef.current) { + skipNextValidationRef.current = false; + setIsLoading(false); + return; + } + try { const userData = await getCurrentUser(token); setUser(userData); @@ -97,6 +105,21 @@ export function AuthProvider({ children }) { const userData = await getCurrentUser(accessToken); persistAuth(accessToken, userData, remember); + skipNextValidationRef.current = true; + setToken(accessToken); + setUser(userData); + return userData; + } + + async function loginWithAccessToken(accessToken, remember = false) { + if (!accessToken) { + throw new Error("Access token is required"); + } + + const userData = await getCurrentUser(accessToken); + persistAuth(accessToken, userData, remember); + + skipNextValidationRef.current = true; setToken(accessToken); setUser(userData); return userData; @@ -114,6 +137,7 @@ export function AuthProvider({ children }) { isAuthenticated, isLoading, login, + loginWithAccessToken, logout, }; diff --git a/frontend/src/pages/AccountPage.js b/frontend/src/pages/AccountPage.js index e4539147..6a9e0818 100644 --- a/frontend/src/pages/AccountPage.js +++ b/frontend/src/pages/AccountPage.js @@ -75,10 +75,6 @@ export default function AccountPage({ sidebarWidth = 220, isDarkMode = true }) { {primaryLabel}
-

- This page is a placeholder so the sidebar doesn't route you back - to the public landing page. -

diff --git a/frontend/src/pages/Auth/GoogleCallbackPage.js b/frontend/src/pages/Auth/GoogleCallbackPage.js new file mode 100644 index 00000000..e527d8ce --- /dev/null +++ b/frontend/src/pages/Auth/GoogleCallbackPage.js @@ -0,0 +1,218 @@ +import React, { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { AlertCircle, Loader2 } from "lucide-react"; + +import "./LoginPage.css"; +import "../Landing/LandingPage.css"; + +import LoginHeader from "./components/LoginHeader"; +import BrandPanel from "./components/BrandPanel"; +import LandingFooter from "../Landing/components/LandingFooter"; +import { useAuth } from "../../context/AuthContext"; + +const CALLBACK_CACHE_KEY = "autoaudit.oauth.google.callback.params"; + +function safeJsonParse(value) { + if (!value) return null; + try { + return JSON.parse(value); + } catch { + return null; + } +} + +function readCachedCallbackParams() { + if (typeof window === "undefined") return null; + try { + return safeJsonParse(window.sessionStorage.getItem(CALLBACK_CACHE_KEY)); + } catch { + return null; + } +} + +function writeCachedCallbackParams(payload) { + if (typeof window === "undefined") return; + try { + window.sessionStorage.setItem(CALLBACK_CACHE_KEY, JSON.stringify(payload)); + } catch { + // best-effort + } +} + +function clearCachedCallbackParams() { + if (typeof window === "undefined") return; + try { + window.sessionStorage.removeItem(CALLBACK_CACHE_KEY); + } catch { + // best-effort + } +} + +function getOAuthParams() { + const rawHash = typeof window !== "undefined" ? window.location.hash : ""; + const hash = rawHash.startsWith("#") ? rawHash.slice(1) : rawHash; + const merged = new URLSearchParams(hash); + + // Some environments/providers return parameters in the query string. + const rawSearch = typeof window !== "undefined" ? window.location.search : ""; + const search = rawSearch.startsWith("?") ? rawSearch.slice(1) : rawSearch; + const searchParams = new URLSearchParams(search); + for (const [key, value] of searchParams.entries()) { + if (!merged.has(key)) merged.set(key, value); + } + + return merged; +} + +const GoogleCallbackPage = () => { + const navigate = useNavigate(); + const auth = useAuth(); + + const [error, setError] = useState(null); + + useEffect(() => { + let cancelled = false; + + async function finish() { + const params = getOAuthParams(); + + // Prefer params from the URL, but fall back to a cached copy. + // React 18 StrictMode intentionally mounts effects twice in development; the first + // run clears the hash, so the second run would otherwise see no token. + const urlPayload = { + access_token: params.get("access_token") || params.get("token") || params.get("accessToken"), + token_type: params.get("token_type") || params.get("tokenType"), + error: params.get("error"), + error_description: params.get("error_description") || params.get("errorDescription"), + }; + + if (urlPayload.access_token || urlPayload.error) { + writeCachedCallbackParams(urlPayload); + } + + const cachedPayload = readCachedCallbackParams(); + const accessToken = urlPayload.access_token || cachedPayload?.access_token; + const oauthError = urlPayload.error || cachedPayload?.error; + const oauthErrorDescription = + urlPayload.error_description || cachedPayload?.error_description; + + if (oauthError) { + if (!cancelled) { + setError(oauthErrorDescription || oauthError); + } + clearCachedCallbackParams(); + return; + } + + if (!accessToken) { + if (!cancelled) { + setError("Missing access token. Please try signing in again."); + } + clearCachedCallbackParams(); + return; + } + + // Remove the token fragment from the URL as soon as possible. + try { + window.history.replaceState({}, document.title, window.location.pathname); + } catch { + // best-effort + } + + try { + // SSO sessions must use sessionStorage (remember=false). + await auth.loginWithAccessToken(accessToken, false); + clearCachedCallbackParams(); + if (!cancelled) { + // Hard redirect so we always leave the callback page after external OAuth. + window.location.replace("/dashboard"); + } + } catch (err) { + if (!cancelled) { + setError(err?.message || "Google sign-in failed. Please try again."); + } + clearCachedCallbackParams(); + } + } + + finish(); + return () => { + cancelled = true; + }; + // We intentionally run this effect only once on initial load of the callback page. + // AuthContext updates after login would otherwise re-run the effect and the URL + // hash may already be cleared, leading to a false “missing access token” error. + }, []); + + return ( +
+ +
+ +
+
+ {error ? ( + <> +
+

Sign-in failed

+

We couldn’t complete Google sign-in. Please try again.

+
+
+ + {error} +
+ + + + ) : ( +
+ +
Please wait while we sign you in.
+
+ )} +
+
+
+ +
+ ); +}; + +export default GoogleCallbackPage; + + diff --git a/frontend/src/pages/Auth/LoginPage.css b/frontend/src/pages/Auth/LoginPage.css index 0048119b..c1e59aca 100644 --- a/frontend/src/pages/Auth/LoginPage.css +++ b/frontend/src/pages/Auth/LoginPage.css @@ -444,12 +444,30 @@ align-items: center; justify-content: center; gap: 0.5rem; + font-weight: 600; cursor: pointer; transition: transform 0.2s ease, border-color 0.2s ease; width: 100%; max-width: 280px; } +.social-btn:focus-visible { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.18); +} + +.social-btn:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + +.social-btn:disabled:hover { + transform: none; + border-color: rgba(59, 130, 246, 0.2); +} + .social-btn:hover { transform: translateY(-2px); border-color: var(--accent); @@ -466,6 +484,15 @@ letter-spacing: 0.5px; } +.social-icon svg { + width: 16px; + height: 16px; +} + +.social-icon--google { + background: rgba(255, 255, 255, 0.08); +} + .signup-text { text-align: center; color: #b0c4de; diff --git a/frontend/src/pages/Auth/components/SignInPanel.js b/frontend/src/pages/Auth/components/SignInPanel.js index 4df15ac1..bfc05327 100644 --- a/frontend/src/pages/Auth/components/SignInPanel.js +++ b/frontend/src/pages/Auth/components/SignInPanel.js @@ -13,6 +13,7 @@ import { useAuth } from "../../../context/AuthContext"; const socialButtons = [ { label: "Google", + provider: "google", icon: (
@@ -189,11 +200,21 @@ const SignInPanel = ({ onLogin, onSignUpClick }) => { Or sign in with
-
+
{socialButtons.map((button) => ( - ))}
diff --git a/frontend/src/pages/Auth/components/SignupFormPanel.js b/frontend/src/pages/Auth/components/SignupFormPanel.js index c41f2367..f3515783 100644 --- a/frontend/src/pages/Auth/components/SignupFormPanel.js +++ b/frontend/src/pages/Auth/components/SignupFormPanel.js @@ -38,6 +38,7 @@ const inputFields = [ const socialButtons = [ { label: "Google", + provider: "google", icon: (
@@ -240,11 +247,21 @@ const SignupFormPanel = ({ formData, onFormChange, onSubmit, onBackToLogin, subm Or sign up with
-
+
{socialButtons.map((button) => ( - ))}
diff --git a/frontend/src/pages/Connections/ConnectionsPage.js b/frontend/src/pages/Connections/ConnectionsPage.js index 931c1a0a..4c99869c 100644 --- a/frontend/src/pages/Connections/ConnectionsPage.js +++ b/frontend/src/pages/Connections/ConnectionsPage.js @@ -106,14 +106,12 @@ const ConnectionsPage = ({ sidebarWidth = 220, isDarkMode = true }) => { e.preventDefault(); setIsEditing(true); setError(null); - try { const updateData = { name: editFormData.name, tenant_id: editFormData.tenant_id, client_id: editFormData.client_id, }; - // Only include client_secret if user entered a new one if (editFormData.client_secret) { updateData.client_secret = editFormData.client_secret; diff --git a/frontend/src/pages/Evidence.js b/frontend/src/pages/Evidence.js index d29ccc89..8c33ef1c 100644 --- a/frontend/src/pages/Evidence.js +++ b/frontend/src/pages/Evidence.js @@ -16,7 +16,7 @@ import './Evidence.css'; // If the validator finds fewer than this number of expected terms for the selected strategy, // treat the scan as "not readable / not relevant" and suppress findings in the UI. -const MIN_VALIDATOR_MATCHED_TERMS = 2; +const MIN_VALIDATOR_MATCHED_TERMS = 1; const EvidenceExtract = ({ evidence }) => { // Frontend helper to render the "Evidence Extract" cell. @@ -84,8 +84,9 @@ const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => { // is meaningful for the chosen strategy. const isLowSignalScan = useMemo(() => { const summary = results?.validator?.summary; - if (!summary) return false; - + if (!summary) { + return false; + } const totalTerms = Number(summary.totalTerms ?? 0); const matchedCount = Number(summary.matchedCount ?? 0); diff --git a/frontend/src/pages/Landing/components/BenefitsSection.js b/frontend/src/pages/Landing/components/BenefitsSection.js index a37f64e7..daef8a2c 100644 --- a/frontend/src/pages/Landing/components/BenefitsSection.js +++ b/frontend/src/pages/Landing/components/BenefitsSection.js @@ -6,7 +6,7 @@ const benefits = [ icon: Timer, title: "Save Time & Resources", description: - "Reduce manual compliance work by 80% so your team can focus on strategic initiatives.", + "Reduce manual compliance work so your team can focus on strategic initiatives.", }, { icon: Target, @@ -18,7 +18,7 @@ const benefits = [ icon: ShieldCheck, title: "Ensure Compliance", description: - "Stay aligned with CIS, NIST, ISO 27001, SOC 2, and other regulatory frameworks.", + "Stay aligned with cybersecurity frameworks as they get updated wit ease", }, { icon: Lightbulb, diff --git a/frontend/src/pages/SettingsPage.js b/frontend/src/pages/SettingsPage.js index 5d927711..890ca2f4 100644 --- a/frontend/src/pages/SettingsPage.js +++ b/frontend/src/pages/SettingsPage.js @@ -1,5 +1,5 @@ import React from "react"; -import { Settings2 } from "lucide-react"; +import { Settings } from "lucide-react"; import "./SettingsPage.css"; export default function SettingsPage({ sidebarWidth = 220, isDarkMode = true }) { @@ -15,7 +15,7 @@ export default function SettingsPage({ sidebarWidth = 220, isDarkMode = true })
- +

Settings

Workspace preferences and application settings.

From d3a7df79a91fcbee5635afa5cd4e919ac85ee698 Mon Sep 17 00:00:00 2001 From: Romil Bijarnia Date: Sun, 4 Jan 2026 16:04:54 +0530 Subject: [PATCH 063/111] working towards builing a connection from autoaudit to m365 --- backend-api/app/api/v1/m365_connections.py | 80 ++++++++- backend-api/app/schemas/m365_connection.py | 6 + backend-api/app/services/m365_graph.py | 155 ++++++++++++++++++ backend-api/pyproject.toml | 2 + backend-api/uv.lock | 129 +++++++++++++++ frontend/src/api/client.js | 6 + .../src/pages/Connections/ConnectionsPage.js | 63 ++++++- 7 files changed, 425 insertions(+), 16 deletions(-) create mode 100644 backend-api/app/services/m365_graph.py diff --git a/backend-api/app/api/v1/m365_connections.py b/backend-api/app/api/v1/m365_connections.py index 59d9c856..1550854d 100644 --- a/backend-api/app/api/v1/m365_connections.py +++ b/backend-api/app/api/v1/m365_connections.py @@ -15,6 +15,7 @@ M365ConnectionTestResult, ) from app.services.encryption import encrypt, decrypt +from app.services.m365_graph import M365ConnectionError, validate_m365_connection router = APIRouter(prefix="/m365-connections", tags=["M365 Connections"]) @@ -26,6 +27,19 @@ async def create_connection( db: AsyncSession = Depends(get_async_session), ) -> M365Connection: """Create a new M365 connection for the current user.""" + # Validate credentials before saving + try: + await validate_m365_connection( + tenant_id=connection_data.tenant_id, + client_id=connection_data.client_id, + client_secret=connection_data.client_secret, + ) + except M365ConnectionError as e: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=str(e), + ) + connection = M365Connection( user_id=current_user.id, name=connection_data.name, @@ -96,7 +110,40 @@ async def update_connection( detail=f"Connection {connection_id} not found", ) - # Update only provided fields + # If tenant_id or client_id changes, require a new secret (can't validate new app without it) + tenant_changed = update_data.tenant_id is not None and update_data.tenant_id != connection.tenant_id + client_changed = update_data.client_id is not None and update_data.client_id != connection.client_id + secret_provided = update_data.client_secret is not None + + if (tenant_changed or client_changed) and not secret_provided: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="client_secret is required when changing tenant_id or client_id", + ) + + # Validate effective credentials if anything credential-related changed + should_validate = tenant_changed or client_changed or secret_provided + if should_validate: + effective_tenant_id = update_data.tenant_id or connection.tenant_id + effective_client_id = update_data.client_id or connection.client_id + effective_secret = ( + update_data.client_secret + if update_data.client_secret is not None + else decrypt(connection.encrypted_client_secret) + ) + try: + await validate_m365_connection( + tenant_id=effective_tenant_id, + client_id=effective_client_id, + client_secret=effective_secret, + ) + except M365ConnectionError as e: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=str(e), + ) + + # Update only provided fields (after validation) if update_data.name is not None: connection.name = update_data.name if update_data.tenant_id is not None: @@ -160,11 +207,26 @@ async def test_connection( # Decrypt credentials client_secret = decrypt(connection.encrypted_client_secret) - # TODO: Implement actual Graph API authentication test - # For now, return a placeholder response - # In production, use MSAL to get a token and verify it works - return M365ConnectionTestResult( - success=True, - message="Connection test not yet implemented - credentials decrypted successfully", - tenant_name=None, - ) + try: + details = await validate_m365_connection( + tenant_id=connection.tenant_id, + client_id=connection.client_id, + client_secret=client_secret, + ) + return M365ConnectionTestResult( + success=True, + message="Connection successful", + tenant_name=details.tenant_display_name, + tenant_display_name=details.tenant_display_name, + default_domain=details.default_domain, + verified_domains=details.verified_domains, + ) + except M365ConnectionError as e: + return M365ConnectionTestResult( + success=False, + message=str(e), + tenant_name=None, + tenant_display_name=None, + default_domain=None, + verified_domains=[], + ) diff --git a/backend-api/app/schemas/m365_connection.py b/backend-api/app/schemas/m365_connection.py index 4917aa17..f32a3945 100644 --- a/backend-api/app/schemas/m365_connection.py +++ b/backend-api/app/schemas/m365_connection.py @@ -47,4 +47,10 @@ class M365ConnectionTestResult(BaseModel): success: bool message: str + # Backwards-compatible display field (legacy) tenant_name: str | None = None + + # Preferred structured tenant details + tenant_display_name: str | None = None + default_domain: str | None = None + verified_domains: list[str] | None = None diff --git a/backend-api/app/services/m365_graph.py b/backend-api/app/services/m365_graph.py new file mode 100644 index 00000000..139c99d0 --- /dev/null +++ b/backend-api/app/services/m365_graph.py @@ -0,0 +1,155 @@ +"""Microsoft 365 (Graph) connection helpers. + +This module validates app-only (client credentials) connectivity to a target tenant +and probes basic tenant details via Microsoft Graph. + +We intentionally keep error messages user-safe so API layers can surface them +directly in HTTP responses without leaking secrets. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from functools import partial + +import anyio +import httpx +from msal import ConfidentialClientApplication + +GRAPH_SCOPES = ["https://graph.microsoft.com/.default"] +GRAPH_ORG_ENDPOINT = "/v1.0/organization?$select=id,displayName,verifiedDomains" + + +@dataclass(frozen=True) +class TenantDetails: + """Normalized tenant details returned from Graph.""" + + tenant_display_name: str | None + default_domain: str | None + verified_domains: list[str] + + +class M365ConnectionError(Exception): + """Raised when M365 auth/probe fails in a user-displayable way.""" + + +def _first_line(value: str | None) -> str: + if not value: + return "Unknown error" + return value.splitlines()[0].strip() or "Unknown error" + + +def _acquire_token_sync(*, tenant_id: str, client_id: str, client_secret: str) -> dict: + """Acquire an app-only token for Microsoft Graph (sync; run in thread).""" + app = ConfidentialClientApplication( + client_id=client_id, + client_credential=client_secret, + authority=f"https://login.microsoftonline.com/{tenant_id}", + ) + return app.acquire_token_for_client(scopes=GRAPH_SCOPES) + + +async def acquire_graph_access_token( + *, tenant_id: str, client_id: str, client_secret: str +) -> str: + """Acquire an app-only access token for Microsoft Graph.""" + tenant_id = (tenant_id or "").strip() + client_id = (client_id or "").strip() + client_secret = (client_secret or "").strip() + + if not tenant_id or not client_id or not client_secret: + raise M365ConnectionError("Missing tenant_id, client_id, or client_secret") + + try: + result = await anyio.to_thread.run_sync( + partial( + _acquire_token_sync, + tenant_id=tenant_id, + client_id=client_id, + client_secret=client_secret, + ) + ) + except Exception as e: + # MSAL can raise (e.g.) ValueError for invalid tenant/authority formats. + raise M365ConnectionError(f"Authentication failed: {_first_line(str(e))}") + + token = result.get("access_token") + if token: + return token + + # Typical errors include AADSTS codes like: + # - AADSTS90002 (tenant not found) + # - AADSTS700016 (app not found) + # - AADSTS7000215 (invalid secret) + desc = _first_line(result.get("error_description") or result.get("error")) + raise M365ConnectionError(f"Authentication failed: {desc}") + + +async def probe_tenant_details(*, access_token: str) -> TenantDetails: + """Probe basic tenant details via Graph /organization.""" + base_url = "https://graph.microsoft.com" + headers = {"Authorization": f"Bearer {access_token}"} + + async with httpx.AsyncClient(timeout=15.0, base_url=base_url) as http: + resp = await http.get(GRAPH_ORG_ENDPOINT, headers=headers) + + if resp.status_code == 403: + raise M365ConnectionError( + "Authenticated, but Graph denied access to /organization. " + "Add Microsoft Graph Application permission 'Organization.Read.All' and grant admin consent." + ) + + if resp.status_code >= 400: + try: + payload = resp.json() + # Graph error shape: {"error": {"code": "...", "message": "..."}} + if isinstance(payload.get("error"), dict): + msg = payload["error"].get("message") + else: + msg = str(payload.get("error") or "") + except Exception: + msg = resp.text + raise M365ConnectionError(f"Graph probe failed: {_first_line(msg)}") + + data = resp.json() + orgs = data.get("value") or [] + if not orgs: + return TenantDetails( + tenant_display_name=None, + default_domain=None, + verified_domains=[], + ) + + org = orgs[0] or {} + display_name = org.get("displayName") + + verified = org.get("verifiedDomains") or [] + verified_domains = [d.get("name") for d in verified if isinstance(d, dict) and d.get("name")] + default_domain = next( + ( + d.get("name") + for d in verified + if isinstance(d, dict) and d.get("isDefault") and d.get("name") + ), + None, + ) + + return TenantDetails( + tenant_display_name=display_name, + default_domain=default_domain, + verified_domains=verified_domains, + ) + + +async def validate_m365_connection( + *, tenant_id: str, client_id: str, client_secret: str +) -> TenantDetails: + """Validate credentials by acquiring a token and probing tenant details.""" + token = await acquire_graph_access_token( + tenant_id=tenant_id, + client_id=client_id, + client_secret=client_secret, + ) + return await probe_tenant_details(access_token=token) + + diff --git a/backend-api/pyproject.toml b/backend-api/pyproject.toml index 8e805c0d..9897ff61 100644 --- a/backend-api/pyproject.toml +++ b/backend-api/pyproject.toml @@ -17,6 +17,8 @@ dependencies = [ # OAuth helpers (Google SSO) "httpx>=0.27.0", "httpx-oauth>=0.15.0", + # Microsoft Graph (client credentials via MSAL) + "msal>=1.31.0", # Multipart upload support for evidence/file endpoints "python-multipart>=0.0.12", # Encryption for credentials at rest diff --git a/backend-api/uv.lock b/backend-api/uv.lock index 9494beaf..7e4d1c10 100644 --- a/backend-api/uv.lock +++ b/backend-api/uv.lock @@ -191,6 +191,7 @@ dependencies = [ { name = "fastapi-users", extra = ["sqlalchemy"] }, { name = "httpx" }, { name = "httpx-oauth" }, + { name = "msal" }, { name = "pydantic" }, { name = "pydantic-settings" }, { name = "python-dotenv" }, @@ -223,6 +224,7 @@ requires-dist = [ { name = "fpdf2", marker = "extra == 'evidence'", specifier = ">=2.8.1" }, { name = "httpx", specifier = ">=0.27.0" }, { name = "httpx-oauth", specifier = ">=0.15.0" }, + { name = "msal", specifier = ">=1.31.0" }, { name = "opencv-python", marker = "extra == 'evidence'", specifier = ">=4.10.0.84" }, { name = "pillow", marker = "extra == 'evidence'", specifier = ">=10.4.0" }, { name = "pydantic", specifier = ">=2.11.7" }, @@ -422,6 +424,95 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, ] +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + [[package]] name = "click" version = "8.2.1" @@ -1091,6 +1182,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] +[[package]] +name = "msal" +version = "1.34.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/0e/c857c46d653e104019a84f22d4494f2119b4fe9f896c92b4b864b3b045cc/msal-1.34.0.tar.gz", hash = "sha256:76ba83b716ea5a6d75b0279c0ac353a0e05b820ca1f6682c0eb7f45190c43c2f", size = 153961, upload-time = "2025-09-22T23:05:48.989Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/dc/18d48843499e278538890dc709e9ee3dea8375f8be8e82682851df1b48b5/msal-1.34.0-py3-none-any.whl", hash = "sha256:f669b1644e4950115da7a176441b0e13ec2975c29528d8b9e81316023676d6e1", size = 116987, upload-time = "2025-09-22T23:05:47.294Z" }, +] + [[package]] name = "numpy" version = "2.2.6" @@ -1549,6 +1654,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e8/02/89e2ed7e85db6c93dfa9e8f691c5087df4e3551ab39081a4d7c6d1f90e05/redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f", size = 279847, upload-time = "2025-08-07T08:10:09.84Z" }, ] +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -1742,6 +1862,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, ] +[[package]] +name = "urllib3" +version = "2.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, +] + [[package]] name = "uvicorn" version = "0.35.0" diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js index 739bfe76..6af6a858 100644 --- a/frontend/src/api/client.js +++ b/frontend/src/api/client.js @@ -145,6 +145,12 @@ export async function deleteConnection(token, id) { return; } +export async function testConnection(token, id) { + return fetchWithAuth(`/v1/m365-connections/${id}/test`, token, { + method: 'POST', + }); +} + // Benchmark endpoints export async function getBenchmarks(token) { return fetchWithAuth('/v1/benchmarks', token); diff --git a/frontend/src/pages/Connections/ConnectionsPage.js b/frontend/src/pages/Connections/ConnectionsPage.js index 4c99869c..b622fbd1 100644 --- a/frontend/src/pages/Connections/ConnectionsPage.js +++ b/frontend/src/pages/Connections/ConnectionsPage.js @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; -import { Plus, Link2, AlertCircle, Loader2, RefreshCw, Pencil, Trash2 } from 'lucide-react'; +import { Plus, Link2, AlertCircle, Loader2, RefreshCw, Pencil, Trash2, CheckCircle2, XCircle } from 'lucide-react'; import { useAuth } from '../../context/AuthContext'; -import { getPlatforms, getConnections, createConnection, updateConnection, deleteConnection } from '../../api/client'; +import { getPlatforms, getConnections, createConnection, updateConnection, deleteConnection, testConnection } from '../../api/client'; import './ConnectionsPage.css'; const ConnectionsPage = ({ sidebarWidth = 220, isDarkMode = true }) => { @@ -28,6 +28,8 @@ const ConnectionsPage = ({ sidebarWidth = 220, isDarkMode = true }) => { }); const [isEditing, setIsEditing] = useState(false); const [deletingId, setDeletingId] = useState(null); + const [testingId, setTestingId] = useState(null); + const [testResults, setTestResults] = useState({}); useEffect(() => { loadData(); @@ -83,8 +85,24 @@ const ConnectionsPage = ({ sidebarWidth = 220, isDarkMode = true }) => { } } - function handleTestConnection() { - alert('Connection testing needs to be implemented'); + async function handleTestConnection(connection) { + setTestingId(connection.id); + setError(null); + try { + const result = await testConnection(token, connection.id); + setTestResults(prev => ({ ...prev, [connection.id]: result })); + if (!result?.success) { + setError(result?.message || 'Connection test failed'); + } + } catch (err) { + setError(err.message || 'Connection test failed'); + setTestResults(prev => ({ + ...prev, + [connection.id]: { success: false, message: err.message || 'Connection test failed' }, + })); + } finally { + setTestingId(null); + } } function startEditing(connection) { @@ -420,6 +438,22 @@ const ConnectionsPage = ({ sidebarWidth = 220, isDarkMode = true }) => {

{connection.name}

+ {testingId === connection.id ? ( + + + Testing + + ) : testResults[connection.id]?.success === true ? ( + + + Connected + + ) : testResults[connection.id]?.success === false ? ( + + + Failed + + ) : null}
@@ -428,15 +462,30 @@ const ConnectionsPage = ({ sidebarWidth = 220, isDarkMode = true }) => { Client ID: {connection.client_id} + {testResults[connection.id]?.tenant_display_name ? ( + + Tenant: {testResults[connection.id].tenant_display_name} + + ) : null} + {testResults[connection.id]?.default_domain ? ( + + Default Domain: {testResults[connection.id].default_domain} + + ) : null}
-

- This page is a placeholder so the sidebar doesn't route you back - to the public landing page. -

diff --git a/frontend/src/pages/Auth/GoogleCallbackPage.js b/frontend/src/pages/Auth/GoogleCallbackPage.js new file mode 100644 index 00000000..e527d8ce --- /dev/null +++ b/frontend/src/pages/Auth/GoogleCallbackPage.js @@ -0,0 +1,218 @@ +import React, { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { AlertCircle, Loader2 } from "lucide-react"; + +import "./LoginPage.css"; +import "../Landing/LandingPage.css"; + +import LoginHeader from "./components/LoginHeader"; +import BrandPanel from "./components/BrandPanel"; +import LandingFooter from "../Landing/components/LandingFooter"; +import { useAuth } from "../../context/AuthContext"; + +const CALLBACK_CACHE_KEY = "autoaudit.oauth.google.callback.params"; + +function safeJsonParse(value) { + if (!value) return null; + try { + return JSON.parse(value); + } catch { + return null; + } +} + +function readCachedCallbackParams() { + if (typeof window === "undefined") return null; + try { + return safeJsonParse(window.sessionStorage.getItem(CALLBACK_CACHE_KEY)); + } catch { + return null; + } +} + +function writeCachedCallbackParams(payload) { + if (typeof window === "undefined") return; + try { + window.sessionStorage.setItem(CALLBACK_CACHE_KEY, JSON.stringify(payload)); + } catch { + // best-effort + } +} + +function clearCachedCallbackParams() { + if (typeof window === "undefined") return; + try { + window.sessionStorage.removeItem(CALLBACK_CACHE_KEY); + } catch { + // best-effort + } +} + +function getOAuthParams() { + const rawHash = typeof window !== "undefined" ? window.location.hash : ""; + const hash = rawHash.startsWith("#") ? rawHash.slice(1) : rawHash; + const merged = new URLSearchParams(hash); + + // Some environments/providers return parameters in the query string. + const rawSearch = typeof window !== "undefined" ? window.location.search : ""; + const search = rawSearch.startsWith("?") ? rawSearch.slice(1) : rawSearch; + const searchParams = new URLSearchParams(search); + for (const [key, value] of searchParams.entries()) { + if (!merged.has(key)) merged.set(key, value); + } + + return merged; +} + +const GoogleCallbackPage = () => { + const navigate = useNavigate(); + const auth = useAuth(); + + const [error, setError] = useState(null); + + useEffect(() => { + let cancelled = false; + + async function finish() { + const params = getOAuthParams(); + + // Prefer params from the URL, but fall back to a cached copy. + // React 18 StrictMode intentionally mounts effects twice in development; the first + // run clears the hash, so the second run would otherwise see no token. + const urlPayload = { + access_token: params.get("access_token") || params.get("token") || params.get("accessToken"), + token_type: params.get("token_type") || params.get("tokenType"), + error: params.get("error"), + error_description: params.get("error_description") || params.get("errorDescription"), + }; + + if (urlPayload.access_token || urlPayload.error) { + writeCachedCallbackParams(urlPayload); + } + + const cachedPayload = readCachedCallbackParams(); + const accessToken = urlPayload.access_token || cachedPayload?.access_token; + const oauthError = urlPayload.error || cachedPayload?.error; + const oauthErrorDescription = + urlPayload.error_description || cachedPayload?.error_description; + + if (oauthError) { + if (!cancelled) { + setError(oauthErrorDescription || oauthError); + } + clearCachedCallbackParams(); + return; + } + + if (!accessToken) { + if (!cancelled) { + setError("Missing access token. Please try signing in again."); + } + clearCachedCallbackParams(); + return; + } + + // Remove the token fragment from the URL as soon as possible. + try { + window.history.replaceState({}, document.title, window.location.pathname); + } catch { + // best-effort + } + + try { + // SSO sessions must use sessionStorage (remember=false). + await auth.loginWithAccessToken(accessToken, false); + clearCachedCallbackParams(); + if (!cancelled) { + // Hard redirect so we always leave the callback page after external OAuth. + window.location.replace("/dashboard"); + } + } catch (err) { + if (!cancelled) { + setError(err?.message || "Google sign-in failed. Please try again."); + } + clearCachedCallbackParams(); + } + } + + finish(); + return () => { + cancelled = true; + }; + // We intentionally run this effect only once on initial load of the callback page. + // AuthContext updates after login would otherwise re-run the effect and the URL + // hash may already be cleared, leading to a false “missing access token” error. + }, []); + + return ( +
+ +
+ +
+
+ {error ? ( + <> +
+

Sign-in failed

+

We couldn’t complete Google sign-in. Please try again.

+
+
+ + {error} +
+ + + + ) : ( +
+ +
Please wait while we sign you in.
+
+ )} +
+
+
+ +
+ ); +}; + +export default GoogleCallbackPage; + + diff --git a/frontend/src/pages/Auth/LoginPage.css b/frontend/src/pages/Auth/LoginPage.css index 0048119b..c1e59aca 100644 --- a/frontend/src/pages/Auth/LoginPage.css +++ b/frontend/src/pages/Auth/LoginPage.css @@ -444,12 +444,30 @@ align-items: center; justify-content: center; gap: 0.5rem; + font-weight: 600; cursor: pointer; transition: transform 0.2s ease, border-color 0.2s ease; width: 100%; max-width: 280px; } +.social-btn:focus-visible { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.18); +} + +.social-btn:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + +.social-btn:disabled:hover { + transform: none; + border-color: rgba(59, 130, 246, 0.2); +} + .social-btn:hover { transform: translateY(-2px); border-color: var(--accent); @@ -466,6 +484,15 @@ letter-spacing: 0.5px; } +.social-icon svg { + width: 16px; + height: 16px; +} + +.social-icon--google { + background: rgba(255, 255, 255, 0.08); +} + .signup-text { text-align: center; color: #b0c4de; diff --git a/frontend/src/pages/Auth/components/SignInPanel.jsx b/frontend/src/pages/Auth/components/SignInPanel.jsx index 4df15ac1..bfc05327 100644 --- a/frontend/src/pages/Auth/components/SignInPanel.jsx +++ b/frontend/src/pages/Auth/components/SignInPanel.jsx @@ -13,6 +13,7 @@ import { useAuth } from "../../../context/AuthContext"; const socialButtons = [ { label: "Google", + provider: "google", icon: (
@@ -189,11 +200,21 @@ const SignInPanel = ({ onLogin, onSignUpClick }) => { Or sign in with
-
+
{socialButtons.map((button) => ( - ))}
diff --git a/frontend/src/pages/Auth/components/SignupFormPanel.jsx b/frontend/src/pages/Auth/components/SignupFormPanel.jsx index c41f2367..f3515783 100644 --- a/frontend/src/pages/Auth/components/SignupFormPanel.jsx +++ b/frontend/src/pages/Auth/components/SignupFormPanel.jsx @@ -38,6 +38,7 @@ const inputFields = [ const socialButtons = [ { label: "Google", + provider: "google", icon: (
@@ -240,11 +247,21 @@ const SignupFormPanel = ({ formData, onFormChange, onSubmit, onBackToLogin, subm Or sign up with
-
+
{socialButtons.map((button) => ( - ))}
diff --git a/frontend/src/pages/Connections/ConnectionsPage.jsx b/frontend/src/pages/Connections/ConnectionsPage.jsx index 931c1a0a..4c99869c 100644 --- a/frontend/src/pages/Connections/ConnectionsPage.jsx +++ b/frontend/src/pages/Connections/ConnectionsPage.jsx @@ -106,14 +106,12 @@ const ConnectionsPage = ({ sidebarWidth = 220, isDarkMode = true }) => { e.preventDefault(); setIsEditing(true); setError(null); - try { const updateData = { name: editFormData.name, tenant_id: editFormData.tenant_id, client_id: editFormData.client_id, }; - // Only include client_secret if user entered a new one if (editFormData.client_secret) { updateData.client_secret = editFormData.client_secret; diff --git a/frontend/src/pages/Evidence.jsx b/frontend/src/pages/Evidence.jsx index e03fc4ae..2fabed55 100644 --- a/frontend/src/pages/Evidence.jsx +++ b/frontend/src/pages/Evidence.jsx @@ -16,7 +16,7 @@ import './Evidence.css'; // If the validator finds fewer than this number of expected terms for the selected strategy, // treat the scan as "not readable / not relevant" and suppress findings in the UI. -const MIN_VALIDATOR_MATCHED_TERMS = 2; +const MIN_VALIDATOR_MATCHED_TERMS = 1; const EvidenceExtract = ({ evidence }) => { // Frontend helper to render the "Evidence Extract" cell. @@ -124,8 +124,9 @@ const Evidence = ({ sidebarWidth = 220, isDarkMode = true }) => { // is meaningful for the chosen strategy. const isLowSignalScan = useMemo(() => { const summary = results?.validator?.summary; - if (!summary) return false; - + if (!summary) { + return false; + } const totalTerms = Number(summary.totalTerms ?? 0); const matchedCount = Number(summary.matchedCount ?? 0); diff --git a/frontend/src/pages/Landing/components/BenefitsSection.jsx b/frontend/src/pages/Landing/components/BenefitsSection.jsx index a37f64e7..daef8a2c 100644 --- a/frontend/src/pages/Landing/components/BenefitsSection.jsx +++ b/frontend/src/pages/Landing/components/BenefitsSection.jsx @@ -6,7 +6,7 @@ const benefits = [ icon: Timer, title: "Save Time & Resources", description: - "Reduce manual compliance work by 80% so your team can focus on strategic initiatives.", + "Reduce manual compliance work so your team can focus on strategic initiatives.", }, { icon: Target, @@ -18,7 +18,7 @@ const benefits = [ icon: ShieldCheck, title: "Ensure Compliance", description: - "Stay aligned with CIS, NIST, ISO 27001, SOC 2, and other regulatory frameworks.", + "Stay aligned with cybersecurity frameworks as they get updated wit ease", }, { icon: Lightbulb, diff --git a/frontend/src/pages/SettingsPage.jsx b/frontend/src/pages/SettingsPage.jsx index 5d927711..890ca2f4 100644 --- a/frontend/src/pages/SettingsPage.jsx +++ b/frontend/src/pages/SettingsPage.jsx @@ -1,5 +1,5 @@ import React from "react"; -import { Settings2 } from "lucide-react"; +import { Settings } from "lucide-react"; import "./SettingsPage.css"; export default function SettingsPage({ sidebarWidth = 220, isDarkMode = true }) { @@ -15,7 +15,7 @@ export default function SettingsPage({ sidebarWidth = 220, isDarkMode = true })
- +

Settings

Workspace preferences and application settings.

From f275a2787c1e017d66751338a67a37ebc183b77e Mon Sep 17 00:00:00 2001 From: Romil Bijarnia Date: Sun, 4 Jan 2026 17:13:28 +0530 Subject: [PATCH 070/111] Addressed previously provided feedback, added an .env example file in the codebase for all contributors to copy from when testing sso locally --- docker-compose.yml | 6 +- docs/GETTING_STARTED.md | 60 +++++++++++++++++-- env.example | 12 ++++ .../Landing/components/BenefitsSection.jsx | 2 +- 4 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 env.example diff --git a/docker-compose.yml b/docker-compose.yml index 7ae25f9b..5ab1bc09 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -85,9 +85,9 @@ services: - BACKEND_PUBLIC_URL=http://localhost:8000 - FRONTEND_URL=http://localhost:3000 - # Google OAuth (SSO) - set these after creating credentials in Google Cloud Console - - GOOGLE_OAUTH_CLIENT_ID=237734019606-8lft9r71d02ljcegsq4d6huglh8ke151.apps.googleusercontent.com - - GOOGLE_OAUTH_CLIENT_SECRET=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 + # Google OAuth (SSO) + - GOOGLE_OAUTH_CLIENT_ID=${GOOGLE_OAUTH_CLIENT_ID:-} + - GOOGLE_OAUTH_CLIENT_SECRET=${GOOGLE_OAUTH_CLIENT_SECRET:-} # Required: Redis URL for Celery task queue - REDIS_URL=redis://redis:6379 diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 1ea22c5e..27294fa0 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -26,6 +26,17 @@ cd AutoAudit The fastest way to get everything running is with Docker Compose. This will start the full stack including the database, Redis, OPA, and all application services. +If you want to test **Google SSO** locally, create a `.env` file (gitignored) and set: + +- `GOOGLE_OAUTH_CLIENT_ID` +- `GOOGLE_OAUTH_CLIENT_SECRET` + +You can start from the template: + +```bash +cp env.example .env +``` + ```bash docker compose --profile all up --build -d ``` @@ -42,12 +53,53 @@ The backend automatically runs database migrations and seeds a default admin use - Email: `admin@example.com` - Password: `admin` -## SSO Test Accounts (Development Only) -These credentials are for local development/testing only. +### Infrastructure Only (default) + +Starts the infrastructure services. Use this when you want to run both the frontend and backend locally. -- Google SSO test user email: `autoauditdev@gmail.com` -- Google SSO test user password: `autoauditlocal123#` +```bash +docker compose up -d +``` + +Services started: +- PostgreSQL on port 5432 +- Redis on port 6379 +- OPA on port 8181 + +### Frontend Development + +If you're working on the frontend and want the backend running in Docker: + +```bash +docker compose --profile frontend-dev up -d +cd frontend +npm install +npm start +``` + +This starts the backend-api in Docker (port 8000), and you run the frontend locally (port 3000). + +### Backend Development + +If you're working on the backend and want the frontend running in Docker: + +```bash +docker compose --profile backend-dev up -d +cd backend-api +uv sync +uv run uvicorn app.main:app --reload --port 8000 +``` + +This starts the frontend in Docker (port 3000), and you run the backend locally (port 8000). + +### Full Stack in Docker + +For testing or demos, run everything in containers: + +```bash +docker compose --profile all up -d +``` ## Module-Specific Setup diff --git a/env.example b/env.example new file mode 100644 index 00000000..74db8963 --- /dev/null +++ b/env.example @@ -0,0 +1,12 @@ +# AutoAudit local development environment variables +# For local dev, create a `.env` file (gitignored) +# +# Usage: +# cp env.example .env + +# Google SSO +GOOGLE_OAUTH_CLIENT_ID= 237734019606-8lft9r71d02ljcegsq4d6huglh8ke151.apps.googleusercontent.com +GOOGLE_OAUTH_CLIENT_SECRET= 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 + + + diff --git a/frontend/src/pages/Landing/components/BenefitsSection.jsx b/frontend/src/pages/Landing/components/BenefitsSection.jsx index daef8a2c..403b7023 100644 --- a/frontend/src/pages/Landing/components/BenefitsSection.jsx +++ b/frontend/src/pages/Landing/components/BenefitsSection.jsx @@ -18,7 +18,7 @@ const benefits = [ icon: ShieldCheck, title: "Ensure Compliance", description: - "Stay aligned with cybersecurity frameworks as they get updated wit ease", + "Stay aligned with cybersecurity frameworks as they get updated with ease", }, { icon: Lightbulb, From 983e85a9c88fe38334043e90c90183172b0c5705 Mon Sep 17 00:00:00 2001 From: Romil Bijarnia Date: Thu, 8 Jan 2026 18:29:47 +0530 Subject: [PATCH 071/111] Fix Google callback JSX and Vite env usage --- ...GoogleCallbackPage.js => GoogleCallbackPage.jsx} | 13 ++++--------- frontend/src/pages/Auth/components/SignInPanel.jsx | 5 +++-- .../src/pages/Auth/components/SignupFormPanel.jsx | 5 +++-- 3 files changed, 10 insertions(+), 13 deletions(-) rename frontend/src/pages/Auth/{GoogleCallbackPage.js => GoogleCallbackPage.jsx} (94%) diff --git a/frontend/src/pages/Auth/GoogleCallbackPage.js b/frontend/src/pages/Auth/GoogleCallbackPage.jsx similarity index 94% rename from frontend/src/pages/Auth/GoogleCallbackPage.js rename to frontend/src/pages/Auth/GoogleCallbackPage.jsx index e527d8ce..550b8481 100644 --- a/frontend/src/pages/Auth/GoogleCallbackPage.js +++ b/frontend/src/pages/Auth/GoogleCallbackPage.jsx @@ -80,7 +80,8 @@ const GoogleCallbackPage = () => { // React 18 StrictMode intentionally mounts effects twice in development; the first // run clears the hash, so the second run would otherwise see no token. const urlPayload = { - access_token: params.get("access_token") || params.get("token") || params.get("accessToken"), + access_token: + params.get("access_token") || params.get("token") || params.get("accessToken"), token_type: params.get("token_type") || params.get("tokenType"), error: params.get("error"), error_description: params.get("error_description") || params.get("errorDescription"), @@ -93,8 +94,7 @@ const GoogleCallbackPage = () => { const cachedPayload = readCachedCallbackParams(); const accessToken = urlPayload.access_token || cachedPayload?.access_token; const oauthError = urlPayload.error || cachedPayload?.error; - const oauthErrorDescription = - urlPayload.error_description || cachedPayload?.error_description; + const oauthErrorDescription = urlPayload.error_description || cachedPayload?.error_description; if (oauthError) { if (!cancelled) { @@ -176,11 +176,7 @@ const GoogleCallbackPage = () => { {error}
- @@ -215,4 +211,3 @@ const GoogleCallbackPage = () => { export default GoogleCallbackPage; - diff --git a/frontend/src/pages/Auth/components/SignInPanel.jsx b/frontend/src/pages/Auth/components/SignInPanel.jsx index bfc05327..3936873f 100644 --- a/frontend/src/pages/Auth/components/SignInPanel.jsx +++ b/frontend/src/pages/Auth/components/SignInPanel.jsx @@ -48,7 +48,8 @@ const SignInPanel = ({ onLogin, onSignUpClick }) => { const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(false); - const apiBaseUrl = process.env.REACT_APP_API_URL; + // Vite exposes env vars via import.meta.env (and they must be prefixed with VITE_) + const apiBaseUrl = import.meta.env.VITE_API_URL; const handleChange = (event) => { const { name, value, type, checked } = event.target; @@ -83,7 +84,7 @@ const SignInPanel = ({ onLogin, onSignUpClick }) => { setError(null); if (!apiBaseUrl) { - setError("Missing API configuration. Please set REACT_APP_API_URL."); + setError("Missing API configuration. Please set VITE_API_URL."); return; } diff --git a/frontend/src/pages/Auth/components/SignupFormPanel.jsx b/frontend/src/pages/Auth/components/SignupFormPanel.jsx index f3515783..6433359a 100644 --- a/frontend/src/pages/Auth/components/SignupFormPanel.jsx +++ b/frontend/src/pages/Auth/components/SignupFormPanel.jsx @@ -67,7 +67,8 @@ const SignupFormPanel = ({ formData, onFormChange, onSubmit, onBackToLogin, subm const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [agreeTerms, setAgreeTerms] = useState(false); const [error, setError] = useState(""); - const apiBaseUrl = process.env.REACT_APP_API_URL; + // Vite exposes env vars via import.meta.env (and they must be prefixed with VITE_) + const apiBaseUrl = import.meta.env.VITE_API_URL; const handleAgreeTermsChange = (event) => { const checked = event.target.checked; @@ -107,7 +108,7 @@ const SignupFormPanel = ({ formData, onFormChange, onSubmit, onBackToLogin, subm const handleSocialSignUp = (provider) => { if (!apiBaseUrl) { - setError("Missing API configuration. Please set REACT_APP_API_URL."); + setError("Missing API configuration. Please set VITE_API_URL."); return; } From 0fc22c7509e7cc818e821108c8e25a33baa6d089 Mon Sep 17 00:00:00 2001 From: Romil Bijarnia Date: Thu, 8 Jan 2026 18:45:52 +0530 Subject: [PATCH 072/111] Fix Vite JSX import issues (remove GoogleCallbackPage.js) --- backend-api/app/api/v1/m365_connections.py | 1 + backend-api/pyproject.toml | 2 + frontend/src/pages/Auth/GoogleCallbackPage.js | 218 ------------------ .../src/pages/Auth/components/SignInPanel.jsx | 21 -- .../pages/Auth/components/SignupFormPanel.jsx | 17 -- .../Landing/components/BenefitsSection.jsx | 1 - 6 files changed, 3 insertions(+), 257 deletions(-) delete mode 100644 frontend/src/pages/Auth/GoogleCallbackPage.js diff --git a/backend-api/app/api/v1/m365_connections.py b/backend-api/app/api/v1/m365_connections.py index 1550854d..c5b0a1e0 100644 --- a/backend-api/app/api/v1/m365_connections.py +++ b/backend-api/app/api/v1/m365_connections.py @@ -213,6 +213,7 @@ async def test_connection( client_id=connection.client_id, client_secret=client_secret, ) + #update m365 connection modal with validation attributes (what exact error happened when we tried the api call) return M365ConnectionTestResult( success=True, message="Connection successful", diff --git a/backend-api/pyproject.toml b/backend-api/pyproject.toml index 8e805c0d..9897ff61 100644 --- a/backend-api/pyproject.toml +++ b/backend-api/pyproject.toml @@ -17,6 +17,8 @@ dependencies = [ # OAuth helpers (Google SSO) "httpx>=0.27.0", "httpx-oauth>=0.15.0", + # Microsoft Graph (client credentials via MSAL) + "msal>=1.31.0", # Multipart upload support for evidence/file endpoints "python-multipart>=0.0.12", # Encryption for credentials at rest diff --git a/frontend/src/pages/Auth/GoogleCallbackPage.js b/frontend/src/pages/Auth/GoogleCallbackPage.js deleted file mode 100644 index e527d8ce..00000000 --- a/frontend/src/pages/Auth/GoogleCallbackPage.js +++ /dev/null @@ -1,218 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; -import { AlertCircle, Loader2 } from "lucide-react"; - -import "./LoginPage.css"; -import "../Landing/LandingPage.css"; - -import LoginHeader from "./components/LoginHeader"; -import BrandPanel from "./components/BrandPanel"; -import LandingFooter from "../Landing/components/LandingFooter"; -import { useAuth } from "../../context/AuthContext"; - -const CALLBACK_CACHE_KEY = "autoaudit.oauth.google.callback.params"; - -function safeJsonParse(value) { - if (!value) return null; - try { - return JSON.parse(value); - } catch { - return null; - } -} - -function readCachedCallbackParams() { - if (typeof window === "undefined") return null; - try { - return safeJsonParse(window.sessionStorage.getItem(CALLBACK_CACHE_KEY)); - } catch { - return null; - } -} - -function writeCachedCallbackParams(payload) { - if (typeof window === "undefined") return; - try { - window.sessionStorage.setItem(CALLBACK_CACHE_KEY, JSON.stringify(payload)); - } catch { - // best-effort - } -} - -function clearCachedCallbackParams() { - if (typeof window === "undefined") return; - try { - window.sessionStorage.removeItem(CALLBACK_CACHE_KEY); - } catch { - // best-effort - } -} - -function getOAuthParams() { - const rawHash = typeof window !== "undefined" ? window.location.hash : ""; - const hash = rawHash.startsWith("#") ? rawHash.slice(1) : rawHash; - const merged = new URLSearchParams(hash); - - // Some environments/providers return parameters in the query string. - const rawSearch = typeof window !== "undefined" ? window.location.search : ""; - const search = rawSearch.startsWith("?") ? rawSearch.slice(1) : rawSearch; - const searchParams = new URLSearchParams(search); - for (const [key, value] of searchParams.entries()) { - if (!merged.has(key)) merged.set(key, value); - } - - return merged; -} - -const GoogleCallbackPage = () => { - const navigate = useNavigate(); - const auth = useAuth(); - - const [error, setError] = useState(null); - - useEffect(() => { - let cancelled = false; - - async function finish() { - const params = getOAuthParams(); - - // Prefer params from the URL, but fall back to a cached copy. - // React 18 StrictMode intentionally mounts effects twice in development; the first - // run clears the hash, so the second run would otherwise see no token. - const urlPayload = { - access_token: params.get("access_token") || params.get("token") || params.get("accessToken"), - token_type: params.get("token_type") || params.get("tokenType"), - error: params.get("error"), - error_description: params.get("error_description") || params.get("errorDescription"), - }; - - if (urlPayload.access_token || urlPayload.error) { - writeCachedCallbackParams(urlPayload); - } - - const cachedPayload = readCachedCallbackParams(); - const accessToken = urlPayload.access_token || cachedPayload?.access_token; - const oauthError = urlPayload.error || cachedPayload?.error; - const oauthErrorDescription = - urlPayload.error_description || cachedPayload?.error_description; - - if (oauthError) { - if (!cancelled) { - setError(oauthErrorDescription || oauthError); - } - clearCachedCallbackParams(); - return; - } - - if (!accessToken) { - if (!cancelled) { - setError("Missing access token. Please try signing in again."); - } - clearCachedCallbackParams(); - return; - } - - // Remove the token fragment from the URL as soon as possible. - try { - window.history.replaceState({}, document.title, window.location.pathname); - } catch { - // best-effort - } - - try { - // SSO sessions must use sessionStorage (remember=false). - await auth.loginWithAccessToken(accessToken, false); - clearCachedCallbackParams(); - if (!cancelled) { - // Hard redirect so we always leave the callback page after external OAuth. - window.location.replace("/dashboard"); - } - } catch (err) { - if (!cancelled) { - setError(err?.message || "Google sign-in failed. Please try again."); - } - clearCachedCallbackParams(); - } - } - - finish(); - return () => { - cancelled = true; - }; - // We intentionally run this effect only once on initial load of the callback page. - // AuthContext updates after login would otherwise re-run the effect and the URL - // hash may already be cleared, leading to a false “missing access token” error. - }, []); - - return ( -
- -
- -
-
- {error ? ( - <> -
-

Sign-in failed

-

We couldn’t complete Google sign-in. Please try again.

-
-
- - {error} -
- - - - ) : ( -
- -
Please wait while we sign you in.
-
- )} -
-
-
- -
- ); -}; - -export default GoogleCallbackPage; - - diff --git a/frontend/src/pages/Auth/components/SignInPanel.jsx b/frontend/src/pages/Auth/components/SignInPanel.jsx index 9126233b..3936873f 100644 --- a/frontend/src/pages/Auth/components/SignInPanel.jsx +++ b/frontend/src/pages/Auth/components/SignInPanel.jsx @@ -51,8 +51,6 @@ const SignInPanel = ({ onLogin, onSignUpClick }) => { // Vite exposes env vars via import.meta.env (and they must be prefixed with VITE_) const apiBaseUrl = import.meta.env.VITE_API_URL; - const apiBaseUrl = process.env.REACT_APP_API_URL; - const handleChange = (event) => { const { name, value, type, checked } = event.target; setFormData((prev) => ({ @@ -100,25 +98,6 @@ const SignInPanel = ({ onLogin, onSignUpClick }) => { setError("Unsupported provider."); }; - const handleSocialLogin = (provider) => { - if (isLoading) return; - setError(null); - - if (!apiBaseUrl) { - setError("Missing API configuration. Please set REACT_APP_API_URL."); - return; - } - - if (provider === "google") { - // Backend-driven OAuth redirect flow. - // The callback will land on: /auth/google/callback#access_token=... - window.location.assign(`${apiBaseUrl}/v1/auth/google/authorize`); - return; - } - - setError("Unsupported provider."); - }; - return (
diff --git a/frontend/src/pages/Auth/components/SignupFormPanel.jsx b/frontend/src/pages/Auth/components/SignupFormPanel.jsx index e17d6ca0..6433359a 100644 --- a/frontend/src/pages/Auth/components/SignupFormPanel.jsx +++ b/frontend/src/pages/Auth/components/SignupFormPanel.jsx @@ -69,7 +69,6 @@ const SignupFormPanel = ({ formData, onFormChange, onSubmit, onBackToLogin, subm const [error, setError] = useState(""); // Vite exposes env vars via import.meta.env (and they must be prefixed with VITE_) const apiBaseUrl = import.meta.env.VITE_API_URL; - const apiBaseUrl = process.env.REACT_APP_API_URL; const handleAgreeTermsChange = (event) => { const checked = event.target.checked; @@ -123,22 +122,6 @@ const SignupFormPanel = ({ formData, onFormChange, onSubmit, onBackToLogin, subm setError("Unsupported provider."); }; - const handleSocialSignUp = (provider) => { - if (!apiBaseUrl) { - setError("Missing API configuration. Please set REACT_APP_API_URL."); - return; - } - - if (provider === "google") { - // Backend-driven OAuth redirect flow. - // The callback will land on: /auth/google/callback#access_token=... - window.location.assign(`${apiBaseUrl}/v1/auth/google/authorize`); - return; - } - - setError("Unsupported provider."); - }; - return (
diff --git a/frontend/src/pages/Landing/components/BenefitsSection.jsx b/frontend/src/pages/Landing/components/BenefitsSection.jsx index 637ca49f..403b7023 100644 --- a/frontend/src/pages/Landing/components/BenefitsSection.jsx +++ b/frontend/src/pages/Landing/components/BenefitsSection.jsx @@ -19,7 +19,6 @@ const benefits = [ title: "Ensure Compliance", description: "Stay aligned with cybersecurity frameworks as they get updated with ease", - "Stay aligned with cybersecurity frameworks as they get updated wit ease", }, { icon: Lightbulb, From 9608996056f7bb3fc96ebe8b9b5df025975ada7f Mon Sep 17 00:00:00 2001 From: Romil Bijarnia Date: Thu, 8 Jan 2026 18:58:00 +0530 Subject: [PATCH 073/111] markdown fix --- docs/GETTING_STARTED.md | 51 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 56b5abbb..43bacd77 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -53,12 +53,53 @@ The backend automatically runs database migrations and seeds a default admin use - Email: `admin@example.com` - Password: `admin` -## SSO Test Accounts (Development Only) -These credentials are for local development/testing only. +### Infrastructure Only (default) -- Google SSO test user email: `autoauditdev@gmail.com` -- Google SSO test user password: `autoauditlocal123#` +Starts the infrastructure services. Use this when you want to run both the frontend and backend locally. + +```bash +docker compose up -d +``` + +Services started: +- PostgreSQL on port 5432 +- Redis on port 6379 +- OPA on port 8181 + +### Frontend Development + +If you're working on the frontend and want the backend running in Docker: + +```bash +docker compose --profile frontend-dev up -d +cd frontend +npm install +npm start +``` + +This starts the backend-api in Docker (port 8000), and you run the frontend locally (port 3000). + +### Backend Development + +If you're working on the backend and want the frontend running in Docker: + +```bash +docker compose --profile backend-dev up -d +cd backend-api +uv sync +uv run uvicorn app.main:app --reload --port 8000 +``` + +This starts the frontend in Docker (port 3000), and you run the backend locally (port 8000). + +### Full Stack in Docker + +For testing or demos, run everything in containers: + +```bash +docker compose --profile all up -d +``` ## Module-Specific Setup @@ -136,4 +177,4 @@ Here's how to confirm everything is working: ## Next Steps -Now that you're set up, head over to [CONTRIBUTING.md](./CONTRIBUTING.md) to learn about the different modules and find the right area to start contributing based on your skills and interests. +Now that you're set up, head over to [CONTRIBUTING.md](./CONTRIBUTING.md) to learn about the different modules and find the right area to start contributing based on your skills and interests. \ No newline at end of file From 2c3c3371ade52f41b8c8c79d7822f240499fd50c Mon Sep 17 00:00:00 2001 From: NYASWA1014 Date: Fri, 9 Jan 2026 01:03:21 +1100 Subject: [PATCH 074/111] Improve login error message and helper text styling --- frontend/src/pages/Auth/LoginPage.css | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/frontend/src/pages/Auth/LoginPage.css b/frontend/src/pages/Auth/LoginPage.css index f1074a1e..53a01d4d 100644 --- a/frontend/src/pages/Auth/LoginPage.css +++ b/frontend/src/pages/Auth/LoginPage.css @@ -295,3 +295,19 @@ .signup-link:hover { color: #f5f6f6; } +.error-message { + margin: 12px 0; + padding: 10px 12px; + border: 1px solid rgba(255, 80, 80, 0.35); + background: rgba(255, 80, 80, 0.12); + color: #ffd6d6; + border-radius: 8px; + font-size: 14px; +} + +.form-helper { + margin-top: 6px; + color: #94a3b8; + font-size: 13px; + line-height: 1.4; +} \ No newline at end of file From 5f22faa8623e2b1594fe15f9781b5da6ec8d8dfd Mon Sep 17 00:00:00 2001 From: Romil Bijarnia Date: Sat, 10 Jan 2026 08:28:27 +0530 Subject: [PATCH 075/111] Improve connection error messaging and secret masking. Show a clearer message for 400 Bad Request responses and keep the edit client secret field masked unless the user enters a new secret. --- .../src/pages/Connections/ConnectionsPage.jsx | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/frontend/src/pages/Connections/ConnectionsPage.jsx b/frontend/src/pages/Connections/ConnectionsPage.jsx index b622fbd1..37e0ada1 100644 --- a/frontend/src/pages/Connections/ConnectionsPage.jsx +++ b/frontend/src/pages/Connections/ConnectionsPage.jsx @@ -1,9 +1,11 @@ import React, { useState, useEffect } from 'react'; import { Plus, Link2, AlertCircle, Loader2, RefreshCw, Pencil, Trash2, CheckCircle2, XCircle } from 'lucide-react'; import { useAuth } from '../../context/AuthContext'; -import { getPlatforms, getConnections, createConnection, updateConnection, deleteConnection, testConnection } from '../../api/client'; +import { APIError, getPlatforms, getConnections, createConnection, updateConnection, deleteConnection, testConnection } from '../../api/client'; import './ConnectionsPage.css'; +const CLIENT_SECRET_MASK = '************'; + const ConnectionsPage = ({ sidebarWidth = 220, isDarkMode = true }) => { const { token } = useAuth(); const [platforms, setPlatforms] = useState([]); @@ -31,6 +33,17 @@ const ConnectionsPage = ({ sidebarWidth = 220, isDarkMode = true }) => { const [testingId, setTestingId] = useState(null); const [testResults, setTestResults] = useState({}); + function getConnectionErrorMessage(err, fallbackMessage) { + // Backend returns 400 Bad Request when M365 auth cannot be established. + if (err instanceof APIError && err.status === 400) { + return 'Authentication not established. Please check your tenant ID, client ID, and client secret and try again.'; + } + if (err?.status === 400) { + return 'Authentication not established. Please check your tenant ID, client ID, and client secret and try again.'; + } + return err?.message || fallbackMessage; + } + useEffect(() => { loadData(); }, [token]); @@ -79,7 +92,7 @@ const ConnectionsPage = ({ sidebarWidth = 220, isDarkMode = true }) => { }); setShowForm(false); } catch (err) { - setError(err.message || 'Failed to create connection'); + setError(getConnectionErrorMessage(err, 'Failed to create connection')); } finally { setIsSubmitting(false); } @@ -95,10 +108,11 @@ const ConnectionsPage = ({ sidebarWidth = 220, isDarkMode = true }) => { setError(result?.message || 'Connection test failed'); } } catch (err) { - setError(err.message || 'Connection test failed'); + const message = getConnectionErrorMessage(err, 'Connection test failed'); + setError(message); setTestResults(prev => ({ ...prev, - [connection.id]: { success: false, message: err.message || 'Connection test failed' }, + [connection.id]: { success: false, message }, })); } finally { setTestingId(null); @@ -111,12 +125,24 @@ const ConnectionsPage = ({ sidebarWidth = 220, isDarkMode = true }) => { name: connection.name, tenant_id: connection.tenant_id, client_id: connection.client_id, - client_secret: '', + // Never expose the actual secret; show a mask so it doesn't look blank. + client_secret: CLIENT_SECRET_MASK, }); } function handleEditChange(e) { - const { name, value } = e.target; + const { name } = e.target; + let { value } = e.target; + + // If the user starts typing while the masked placeholder is present, + // ensure we don't keep the mask characters in state. + if ( + name === 'client_secret' && + editFormData.client_secret === CLIENT_SECRET_MASK && + value.startsWith(CLIENT_SECRET_MASK) + ) { + value = value.slice(CLIENT_SECRET_MASK.length); + } setEditFormData(prev => ({ ...prev, [name]: value })); } @@ -131,7 +157,7 @@ const ConnectionsPage = ({ sidebarWidth = 220, isDarkMode = true }) => { client_id: editFormData.client_id, }; // Only include client_secret if user entered a new one - if (editFormData.client_secret) { + if (editFormData.client_secret && editFormData.client_secret !== CLIENT_SECRET_MASK) { updateData.client_secret = editFormData.client_secret; } @@ -141,7 +167,7 @@ const ConnectionsPage = ({ sidebarWidth = 220, isDarkMode = true }) => { ); setEditingConnection(null); } catch (err) { - setError(err.message || 'Failed to update connection'); + setError(getConnectionErrorMessage(err, 'Failed to update connection')); } finally { setIsEditing(false); } @@ -392,7 +418,12 @@ const ConnectionsPage = ({ sidebarWidth = 220, isDarkMode = true }) => { type="password" value={editFormData.client_secret} onChange={handleEditChange} - placeholder="Leave blank to keep current secret" + placeholder="Enter a new client secret" + onFocus={(e) => { + if (e.target.value === CLIENT_SECRET_MASK) { + e.target.select(); + } + }} disabled={isEditing} />
From 9137aaf2d3aad34d808d014682ee1da70023669d Mon Sep 17 00:00:00 2001 From: Tina Relucio Date: Sun, 11 Jan 2026 02:09:14 +0800 Subject: [PATCH 076/111] updated section 2.1.8, 2.1.9, 2.1.10 --- ...n_dkim_signing_config_20260107_233140.json | 11 - ..._dns_security_records_20260107_233107.json | 22 -- .../test_summary_20260107_233719.json | 299 ------------------ .../2.1.10_DMARC_records_published.rego | 17 +- .../v6.0.0/2.1.8_SPF_records_published.rego | 13 +- .../v6.0.0/2.1.9_DKIM_is_enabled.rego | 22 +- 6 files changed, 34 insertions(+), 350 deletions(-) delete mode 100644 engine/examples/exchange_authentication_dkim_signing_config_20260107_233140.json delete mode 100644 engine/examples/exchange_dns_dns_security_records_20260107_233107.json delete mode 100644 engine/examples/test_summary_20260107_233719.json diff --git a/engine/examples/exchange_authentication_dkim_signing_config_20260107_233140.json b/engine/examples/exchange_authentication_dkim_signing_config_20260107_233140.json deleted file mode 100644 index 80aa43b6..00000000 --- a/engine/examples/exchange_authentication_dkim_signing_config_20260107_233140.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "collector_id": "exchange.authentication.dkim_signing_config", - "timestamp": "2026-01-07T23:31:40.449291", - "elapsed_seconds": 7.058, - "data": { - "dkim_configs": [], - "total_domains": 0, - "domains_with_dkim_enabled": [], - "domains_with_dkim_disabled": [] - } -} \ No newline at end of file diff --git a/engine/examples/exchange_dns_dns_security_records_20260107_233107.json b/engine/examples/exchange_dns_dns_security_records_20260107_233107.json deleted file mode 100644 index b4532697..00000000 --- a/engine/examples/exchange_dns_dns_security_records_20260107_233107.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "collector_id": "exchange.dns.dns_security_records", - "timestamp": "2026-01-07T23:31:07.715307", - "elapsed_seconds": 0.611, - "data": { - "domains": [ - { - "domain": "t8sjf.onmicrosoft.com", - "is_verified": true, - "is_default": true, - "is_initial": true, - "authentication_type": "Managed", - "spf_record": "v=spf1 include:spf.protection.outlook.com -all", - "dmarc_record": null, - "dmarc_policy": null, - "spf_error": null, - "dmarc_error": "DMARC record not found" - } - ], - "total_domains": 1 - } -} \ No newline at end of file diff --git a/engine/examples/test_summary_20260107_233719.json b/engine/examples/test_summary_20260107_233719.json deleted file mode 100644 index 13bc43bc..00000000 --- a/engine/examples/test_summary_20260107_233719.json +++ /dev/null @@ -1,299 +0,0 @@ -{ - "timestamp": "2026-01-07T23:37:19.601266", - "summary": { - "ok": 48, - "not_implemented": 0, - "powershell_errors": 0, - "errors": 0 - }, - "results": [ - { - "collector_id": "compliance.report_submission_policy", - "status": "OK", - "elapsed_seconds": 6.23, - "error": null - }, - { - "collector_id": "entra.applications.apps_and_services_settings", - "status": "OK", - "elapsed_seconds": 0.45, - "error": null - }, - { - "collector_id": "entra.applications.forms_settings", - "status": "OK", - "elapsed_seconds": 1.596, - "error": null - }, - { - "collector_id": "entra.applications.service_principals", - "status": "OK", - "elapsed_seconds": 0.983, - "error": null - }, - { - "collector_id": "entra.authentication.authentication_methods", - "status": "OK", - "elapsed_seconds": 1.29, - "error": null - }, - { - "collector_id": "entra.authentication.mfa_fatigue_protection", - "status": "OK", - "elapsed_seconds": 0.922, - "error": null - }, - { - "collector_id": "entra.authentication.mfa_registration_report", - "status": "OK", - "elapsed_seconds": 0.748, - "error": null - }, - { - "collector_id": "entra.authentication.password_protection", - "status": "OK", - "elapsed_seconds": 0.35, - "error": null - }, - { - "collector_id": "entra.conditional_access.legacy_auth_block", - "status": "OK", - "elapsed_seconds": 4.559, - "error": null - }, - { - "collector_id": "entra.conditional_access.policies", - "status": "OK", - "elapsed_seconds": 3.061, - "error": null - }, - { - "collector_id": "entra.devices.device_management_settings", - "status": "OK", - "elapsed_seconds": 1.689, - "error": null - }, - { - "collector_id": "entra.devices.device_registration_policy", - "status": "OK", - "elapsed_seconds": 3.246, - "error": null - }, - { - "collector_id": "entra.devices.enrollment_restrictions", - "status": "OK", - "elapsed_seconds": 0.899, - "error": null - }, - { - "collector_id": "entra.domains.domains", - "status": "OK", - "elapsed_seconds": 0.526, - "error": null - }, - { - "collector_id": "entra.domains.password_policy", - "status": "OK", - "elapsed_seconds": 0.317, - "error": null - }, - { - "collector_id": "entra.governance.access_reviews", - "status": "OK", - "elapsed_seconds": 1.985, - "error": null - }, - { - "collector_id": "entra.governance.pim_role_policies", - "status": "OK", - "elapsed_seconds": 4.372, - "error": null - }, - { - "collector_id": "entra.groups.groups", - "status": "OK", - "elapsed_seconds": 0.343, - "error": null - }, - { - "collector_id": "entra.policies.activity_timeout_policy", - "status": "OK", - "elapsed_seconds": 0.275, - "error": null - }, - { - "collector_id": "entra.policies.admin_consent_request_policy", - "status": "OK", - "elapsed_seconds": 4.294, - "error": null - }, - { - "collector_id": "entra.policies.authorization_policy", - "status": "OK", - "elapsed_seconds": 0.278, - "error": null - }, - { - "collector_id": "entra.policies.b2b_policy", - "status": "OK", - "elapsed_seconds": 0.665, - "error": null - }, - { - "collector_id": "entra.roles.cloud_only_admins", - "status": "OK", - "elapsed_seconds": 1.67, - "error": null - }, - { - "collector_id": "entra.roles.directory_roles", - "status": "OK", - "elapsed_seconds": 1.392, - "error": null - }, - { - "collector_id": "entra.roles.privileged_roles", - "status": "OK", - "elapsed_seconds": 0.704, - "error": null - }, - { - "collector_id": "entra.users.users", - "status": "OK", - "elapsed_seconds": 0.364, - "error": null - }, - { - "collector_id": "exchange.audit.admin_audit_log_config", - "status": "OK", - "elapsed_seconds": 7.113, - "error": null - }, - { - "collector_id": "exchange.authentication.dkim_signing_config", - "status": "OK", - "elapsed_seconds": 6.242, - "error": null - }, - { - "collector_id": "exchange.dns.dns_security_records", - "status": "OK", - "elapsed_seconds": 0.417, - "error": null - }, - { - "collector_id": "exchange.mailbox.mailbox_audit", - "status": "OK", - "elapsed_seconds": 8.664, - "error": null - }, - { - "collector_id": "exchange.mailbox.mailbox_audit_actions", - "status": "OK", - "elapsed_seconds": 10.488, - "error": null - }, - { - "collector_id": "exchange.mailbox.mailboxes", - "status": "OK", - "elapsed_seconds": 10.817, - "error": null - }, - { - "collector_id": "exchange.mailbox.role_assignment_policy", - "status": "OK", - "elapsed_seconds": 7.272, - "error": null - }, - { - "collector_id": "exchange.organization.organization_config", - "status": "OK", - "elapsed_seconds": 6.663, - "error": null - }, - { - "collector_id": "exchange.organization.owa_mailbox_policy", - "status": "OK", - "elapsed_seconds": 6.674, - "error": null - }, - { - "collector_id": "exchange.organization.sharing_policy", - "status": "OK", - "elapsed_seconds": 6.308, - "error": null - }, - { - "collector_id": "exchange.organization.transport_config", - "status": "OK", - "elapsed_seconds": 7.037, - "error": null - }, - { - "collector_id": "exchange.protection.anti_phish_policy", - "status": "OK", - "elapsed_seconds": 6.807, - "error": null - }, - { - "collector_id": "exchange.protection.atp_policy_o365", - "status": "OK", - "elapsed_seconds": 6.559, - "error": null - }, - { - "collector_id": "exchange.protection.hosted_connection_filter", - "status": "OK", - "elapsed_seconds": 6.321, - "error": null - }, - { - "collector_id": "exchange.protection.hosted_content_filter", - "status": "OK", - "elapsed_seconds": 6.638, - "error": null - }, - { - "collector_id": "exchange.protection.hosted_outbound_spam_filter", - "status": "OK", - "elapsed_seconds": 8.487, - "error": null - }, - { - "collector_id": "exchange.protection.malware_filter_policy", - "status": "OK", - "elapsed_seconds": 6.271, - "error": null - }, - { - "collector_id": "exchange.protection.safe_attachment_policy", - "status": "OK", - "elapsed_seconds": 6.665, - "error": null - }, - { - "collector_id": "exchange.protection.safe_links_policy", - "status": "OK", - "elapsed_seconds": 6.906, - "error": null - }, - { - "collector_id": "exchange.protection.teams_protection_policy", - "status": "OK", - "elapsed_seconds": 7.287, - "error": null - }, - { - "collector_id": "exchange.transport.external_in_outlook", - "status": "OK", - "elapsed_seconds": 7.236, - "error": null - }, - { - "collector_id": "exchange.transport.transport_rules", - "status": "OK", - "elapsed_seconds": 7.35, - "error": null - } - ] -} \ No newline at end of file diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.10_DMARC_records_published.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.10_DMARC_records_published.rego index 4253fc32..384d1cce 100644 --- a/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.10_DMARC_records_published.rego +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.10_DMARC_records_published.rego @@ -43,11 +43,18 @@ result := output if { } } -dmarc_record_published(domain) { - some i - record := domain.dns_records[i] - record.type == "TXT" - startswith(record.value, "v=DMARC1") +dmarc_record_published(domain) if { + dmarc := domain.dmarc_record + dmarc != "" + startswith(dmarc, "v=DMARC1") + contains(dmarc, "p=quarantine") +} + +dmarc_record_published(domain) if { + dmarc := domain.dmarc_record + dmarc != "" + startswith(dmarc, "v=DMARC1") + contains(dmarc, "p=reject") } generate_message(true, _) := "All Exchange domains have DMARC records published." diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.8_SPF_records_published.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.8_SPF_records_published.rego index 2c9abe5e..14118661 100644 --- a/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.8_SPF_records_published.rego +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.8_SPF_records_published.rego @@ -29,8 +29,6 @@ result := output if { compliant := count(spf_issues) == 0 - # Compliant when SPF records published for each domain created - output := { "compliant": compliant, "message": generate_message(compliant, spf_issues), @@ -43,15 +41,18 @@ result := output if { } } -spf_record_published(domain) { - count(domain.spf_records) > 0 +spf_record_published(domain) if { + record := trim(domain.spf_record, " \t\r\n") + record != "" + startswith(lower(record), "v=spf1") } generate_message(true, _) := "All Exchange domains have SPF records published." + generate_message(false, spf_issues) := sprintf( - "%d domain(s) are missing SPF records", + "%d domain(s) do not have a valid SPF record (v=spf1...) published", [count(spf_issues)] ) generate_affected_resources(true, _) := [] -generate_affected_resources(false, spf_issues) := [d.name | d := spf_issues[_]] \ No newline at end of file +generate_affected_resources(false, spf_issues) := [d.domain | d := spf_issues[_]] \ No newline at end of file diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.9_DKIM_is_enabled.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.9_DKIM_is_enabled.rego index e4d9da55..6b6704a9 100644 --- a/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.9_DKIM_is_enabled.rego +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.9_DKIM_is_enabled.rego @@ -21,18 +21,25 @@ package cis.microsoft_365_foundations.v6_0_0.control_2_1_9 default result := {"compliant": false, "message": "Evaluation failed"} -result := output if { - dkim_enabled := input.dkim_enabled +# Compute dkim_enabled from per-domain lists +dkim_enabled := true if count(input.domains_with_dkim_disabled) == 0 +dkim_enabled := false if count(input.domains_with_dkim_disabled) > 0 +dkim_enabled := null if { + not input.domains_with_dkim_enabled + not input.domains_with_dkim_disabled +} - # Compliant when DKIM is enabled +result := output if { compliant := dkim_enabled == true output := { "compliant": compliant, "message": generate_message(dkim_enabled), - "affected_resources": generate_affected_resources(compliant), + "affected_resources": generate_affected_resources(compliant, input), "details": { - "dkim_signing_enabled": dkim_enabled + "dkim_signing_enabled": dkim_enabled, + "domains_with_dkim_enabled": input.domains_with_dkim_enabled, + "domains_with_dkim_disabled": input.domains_with_dkim_disabled } } } @@ -52,5 +59,6 @@ generate_message(dkim_enabled) := msg if { msg := "Unable to determine DKIM signing status" } -generate_affected_resources(true) := [] -generate_affected_resources(false) := ["DKIM signing is not enabled"] \ No newline at end of file +generate_affected_resources(true, _) := [] +generate_affected_resources(false, data_input) := data_input.domains_with_dkim_disabled +generate_affected_resources(null, _) := ["DKIM signing status unknown"] From 581cdd21ee510860e56bf169e0088dc9974127e7 Mon Sep 17 00:00:00 2001 From: Tina Relucio Date: Sun, 11 Jan 2026 02:53:49 +0800 Subject: [PATCH 077/111] modified section 2.1.10 --- .../2.1.10_DMARC_records_published.rego | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.10_DMARC_records_published.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.10_DMARC_records_published.rego index 384d1cce..314f8239 100644 --- a/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.10_DMARC_records_published.rego +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.10_DMARC_records_published.rego @@ -44,17 +44,17 @@ result := output if { } dmarc_record_published(domain) if { - dmarc := domain.dmarc_record + domain.dmarc_record + dmarc := lower(domain.dmarc_record) dmarc != "" - startswith(dmarc, "v=DMARC1") - contains(dmarc, "p=quarantine") -} + startswith(dmarc, "v=dmarc1") -dmarc_record_published(domain) if { - dmarc := domain.dmarc_record - dmarc != "" - startswith(dmarc, "v=DMARC1") - contains(dmarc, "p=reject") + tags := [trim_space(t) | t := split(dmarc, ";")[_]] + some i + tag := tags[i] + startswith(tag, "p=") + policy := trim_space(substring(tag, 2, count(tag)-2)) + policy in {"quarantine", "reject"} } generate_message(true, _) := "All Exchange domains have DMARC records published." @@ -64,4 +64,4 @@ generate_message(false, dmarc_issues) := sprintf( ) generate_affected_resources(true, _) := [] -generate_affected_resources(false, dmarc_issues) := [d.name | d := dmarc_issues[_]] \ No newline at end of file +generate_affected_resources(false, dmarc_issues) := [d.domain | d := dmarc_issues[_]] From aa29e30a33d6f99eeea7f22155b30df2a0973bf5 Mon Sep 17 00:00:00 2001 From: Tina Relucio Date: Sun, 11 Jan 2026 08:24:25 +0800 Subject: [PATCH 078/111] modified section 2.1.9 2.1.10 --- .../v6.0.0/2.1.10_DMARC_records_published.rego | 2 +- .../microsoft-365-foundations/v6.0.0/2.1.9_DKIM_is_enabled.rego | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.10_DMARC_records_published.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.10_DMARC_records_published.rego index 314f8239..31cb8fc7 100644 --- a/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.10_DMARC_records_published.rego +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.10_DMARC_records_published.rego @@ -59,7 +59,7 @@ dmarc_record_published(domain) if { generate_message(true, _) := "All Exchange domains have DMARC records published." generate_message(false, dmarc_issues) := sprintf( - "%d domain(s) are missing DMARC records", + "%d domain(s) do not meet DMARC enforcement requirements (missing record or p policy is not quarantine/reject)", [count(dmarc_issues)] ) diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.9_DKIM_is_enabled.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.9_DKIM_is_enabled.rego index 6b6704a9..a3752027 100644 --- a/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.9_DKIM_is_enabled.rego +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.9_DKIM_is_enabled.rego @@ -35,7 +35,7 @@ result := output if { output := { "compliant": compliant, "message": generate_message(dkim_enabled), - "affected_resources": generate_affected_resources(compliant, input), + "affected_resources": generate_affected_resources(dkim_enabled, input), "details": { "dkim_signing_enabled": dkim_enabled, "domains_with_dkim_enabled": input.domains_with_dkim_enabled, From b81edc4b4da214147c92337c85f54cebdfa856ef Mon Sep 17 00:00:00 2001 From: David Hartley Date: Sun, 11 Jan 2026 16:31:31 +1100 Subject: [PATCH 079/111] Use a much smaller base image for the local container --- engine/docker/powershell/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/docker/powershell/Dockerfile b/engine/docker/powershell/Dockerfile index fce0c49d..fa427fd6 100644 --- a/engine/docker/powershell/Dockerfile +++ b/engine/docker/powershell/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:10.0 +FROM mcr.microsoft.com/powershell:7.5-mariner-2.0 # Install PowerShell modules for M365 management RUN pwsh -NoProfile -Command " \ From fee6ff03f24cb8eb185dcf03bd16e95656a06158 Mon Sep 17 00:00:00 2001 From: David Hartley Date: Sun, 11 Jan 2026 16:31:56 +1100 Subject: [PATCH 080/111] Add a very simple FastAPI wrapper for the powershell service --- engine/powershell/Dockerfile | 26 ++++ engine/powershell/service/executor.py | 171 +++++++++++++++++++++++ engine/powershell/service/main.py | 49 +++++++ engine/powershell/service/pyproject.toml | 10 ++ engine/powershell/service/schemas.py | 40 ++++++ 5 files changed, 296 insertions(+) create mode 100644 engine/powershell/Dockerfile create mode 100644 engine/powershell/service/executor.py create mode 100644 engine/powershell/service/main.py create mode 100644 engine/powershell/service/pyproject.toml create mode 100644 engine/powershell/service/schemas.py diff --git a/engine/powershell/Dockerfile b/engine/powershell/Dockerfile new file mode 100644 index 00000000..a7e06373 --- /dev/null +++ b/engine/powershell/Dockerfile @@ -0,0 +1,26 @@ +FROM mcr.microsoft.com/powershell:7.5-mariner-2.0 + +# Install Python, curl (for healthcheck), and tar (for uv installer) +RUN tdnf install -y python3 curl tar && tdnf clean all + +# Install uv +RUN curl -LsSf https://astral.sh/uv/install.sh | sh +ENV PATH="/root/.local/bin:$PATH" + +# Install PowerShell modules +RUN pwsh -NoProfile -Command " \ + Set-PSRepository PSGallery -InstallationPolicy Trusted; \ + Install-Module -Name ExchangeOnlineManagement -Scope AllUsers -Force; \ + Install-Module -Name MicrosoftTeams -Scope AllUsers -Force \ +" + +# Copy service code and install Python dependencies +COPY service/ /app/ +WORKDIR /app +RUN uv pip install --system -r pyproject.toml + +# Expose port +EXPOSE 8001 + +# Run FastAPI service +CMD ["python3", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001"] diff --git a/engine/powershell/service/executor.py b/engine/powershell/service/executor.py new file mode 100644 index 00000000..de0881cd --- /dev/null +++ b/engine/powershell/service/executor.py @@ -0,0 +1,171 @@ +"""PowerShell command executor.""" + +import json +import os +import subprocess +from typing import Any, Dict, Optional + + +class PowerShellExecutionError(Exception): + """Raised when PowerShell execution fails.""" + + pass + + +def build_param_string(params: Dict[str, Any]) -> str: + """Build PowerShell parameter string from dict. + + Args: + params: Dictionary of parameter names to values + + Returns: + PowerShell parameter string (e.g., ' -Name "value" -Enabled:$true') + """ + param_str = "" + for key, value in params.items(): + if isinstance(value, bool): + param_str += f" -{key}:${str(value).lower()}" + elif isinstance(value, str): + # Escape double quotes in string values + escaped = value.replace('"', '`"') + param_str += f' -{key} "{escaped}"' + else: + param_str += f" -{key} {value}" + return param_str + + +def build_script( + module: str, + cmdlet: str, + params: Dict[str, Any], + tenant_id: str, +) -> str: + """Build the PowerShell script to execute. + + Args: + module: The module to use (ExchangeOnline, Compliance, Teams) + cmdlet: The cmdlet to run + params: Parameters for the cmdlet + tenant_id: Azure AD tenant ID + + Returns: + PowerShell script as a string + """ + param_str = build_param_string(params) + + if module == "ExchangeOnline": + return f''' +Import-Module ExchangeOnlineManagement +Connect-ExchangeOnline -AccessToken $env:EXO_TOKEN -Organization "{tenant_id}" -ShowBanner:$false +try {{ + $result = {cmdlet}{param_str} + if ($null -eq $result) {{ + Write-Output 'null' + }} else {{ + $result | ConvertTo-Json -Depth 10 + }} +}} finally {{ + Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue +}} +''' + elif module == "Compliance": + return f''' +Import-Module ExchangeOnlineManagement +Connect-IPPSSession -AccessToken $env:EXO_TOKEN -Organization "{tenant_id}" -ShowBanner:$false +try {{ + $result = {cmdlet}{param_str} + if ($null -eq $result) {{ + Write-Output 'null' + }} else {{ + $result | ConvertTo-Json -Depth 10 + }} +}} finally {{ + Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue +}} +''' + elif module == "Teams": + return f''' +Import-Module MicrosoftTeams +Connect-MicrosoftTeams -AccessTokens @($env:GRAPH_TOKEN, $env:TEAMS_TOKEN) -TenantId "{tenant_id}" +try {{ + $result = {cmdlet}{param_str} + if ($null -eq $result) {{ + Write-Output 'null' + }} else {{ + $result | ConvertTo-Json -Depth 10 + }} +}} finally {{ + Disconnect-MicrosoftTeams -ErrorAction SilentlyContinue +}} +''' + else: + raise ValueError(f"Unsupported module: {module}") + + +def execute_cmdlet( + module: str, + cmdlet: str, + params: Dict[str, Any], + tenant_id: str, + token: str, + graph_token: Optional[str] = None, +) -> Dict[str, Any]: + """Execute a PowerShell cmdlet and return the result. + + Args: + module: PowerShell module (ExchangeOnline, Compliance, Teams) + cmdlet: The cmdlet to run + params: Parameters for the cmdlet + tenant_id: Azure AD tenant ID + token: Access token for Exchange/Compliance + graph_token: Graph API token (required for Teams) + + Returns: + Parsed JSON output from the cmdlet + + Raises: + PowerShellExecutionError: If execution fails + ValueError: If Teams module requested without graph_token + """ + if module == "Teams" and not graph_token: + raise ValueError("Teams module requires graph_token") + + # Build the script + script = build_script(module, cmdlet, params, tenant_id) + + # Set up environment with tokens + env = os.environ.copy() + if module == "Teams": + env["GRAPH_TOKEN"] = graph_token + env["TEAMS_TOKEN"] = token + else: + env["EXO_TOKEN"] = token + + # Execute PowerShell + try: + proc = subprocess.run( + ["pwsh", "-NoProfile", "-NonInteractive", "-Command", script], + capture_output=True, + text=True, + timeout=120, + env=env, + ) + except subprocess.TimeoutExpired: + raise PowerShellExecutionError("PowerShell execution timed out after 120 seconds") + except Exception as e: + raise PowerShellExecutionError(f"Failed to execute PowerShell: {e}") + + if proc.returncode != 0: + raise PowerShellExecutionError(f"PowerShell execution failed:\n{proc.stderr}") + + # Parse JSON output + stdout = proc.stdout.strip() + if not stdout or stdout == "null": + return None + + try: + return json.loads(stdout) + except json.JSONDecodeError as e: + raise PowerShellExecutionError( + f"Failed to parse PowerShell output as JSON:\n{stdout}\nError: {e}" + ) diff --git a/engine/powershell/service/main.py b/engine/powershell/service/main.py new file mode 100644 index 00000000..95f84988 --- /dev/null +++ b/engine/powershell/service/main.py @@ -0,0 +1,49 @@ +"""FastAPI service for PowerShell cmdlet execution.""" + +from fastapi import FastAPI, HTTPException + +from schemas import ExecuteRequest, ExecuteResponse, HealthResponse +from executor import execute_cmdlet, PowerShellExecutionError + +app = FastAPI( + title="PowerShell Service", + description="HTTP service for executing M365 PowerShell cmdlets", + version="1.0.0", +) + + +@app.get("/health", response_model=HealthResponse) +async def health_check(): + """Health check endpoint.""" + return HealthResponse(status="ok") + + +@app.post("/execute", response_model=ExecuteResponse) +async def execute(request: ExecuteRequest): + """Execute a PowerShell cmdlet. + + Args: + request: Execution request with module, cmdlet, params, and auth + + Returns: + ExecuteResponse with success status and data or error + """ + try: + result = execute_cmdlet( + module=request.module, + cmdlet=request.cmdlet, + params=request.params, + tenant_id=request.tenant_id, + token=request.token, + graph_token=request.graph_token, + ) + return ExecuteResponse(success=True, data=result) + except ValueError as e: + # Invalid request (e.g., Teams without graph_token) + raise HTTPException(status_code=400, detail=str(e)) + except PowerShellExecutionError as e: + # PowerShell execution failed + return ExecuteResponse(success=False, error=str(e)) + except Exception as e: + # Unexpected error + return ExecuteResponse(success=False, error=f"Unexpected error: {e}") diff --git a/engine/powershell/service/pyproject.toml b/engine/powershell/service/pyproject.toml new file mode 100644 index 00000000..56067bd7 --- /dev/null +++ b/engine/powershell/service/pyproject.toml @@ -0,0 +1,10 @@ +[project] +name = "powershell-service" +version = "1.0.0" +description = "HTTP service for executing M365 PowerShell cmdlets" +requires-python = ">=3.9" +dependencies = [ + "fastapi", + "uvicorn", + "pydantic", +] diff --git a/engine/powershell/service/schemas.py b/engine/powershell/service/schemas.py new file mode 100644 index 00000000..abbed3a2 --- /dev/null +++ b/engine/powershell/service/schemas.py @@ -0,0 +1,40 @@ +"""Pydantic schemas for PowerShell service API.""" + +from typing import Any, Dict, Literal, Optional + +from pydantic import BaseModel, Field + + +class ExecuteRequest(BaseModel): + """Request to execute a PowerShell cmdlet.""" + + module: Literal["ExchangeOnline", "Compliance", "Teams"] = Field( + description="PowerShell module to use" + ) + cmdlet: str = Field( + description="PowerShell cmdlet to execute (e.g., Get-OrganizationConfig)" + ) + params: Dict[str, Any] = Field( + default_factory=dict, + description="Parameters to pass to the cmdlet", + ) + tenant_id: str = Field(description="Azure AD tenant ID") + token: str = Field(description="Access token for Exchange/Compliance") + graph_token: Optional[str] = Field( + default=None, + description="Graph API token (required for Teams module)", + ) + + +class ExecuteResponse(BaseModel): + """Response from PowerShell cmdlet execution.""" + + success: bool = Field(description="Whether execution succeeded") + data: Any = Field(default=None, description="Cmdlet output as JSON") + error: Optional[str] = Field(default=None, description="Error message if failed") + + +class HealthResponse(BaseModel): + """Health check response.""" + + status: str = "ok" From eb627c7efb1cca8cac2cdec0bdd11c7359a2a6d9 Mon Sep 17 00:00:00 2001 From: David Hartley Date: Sun, 11 Jan 2026 16:32:09 +1100 Subject: [PATCH 081/111] Support the powershell service from the worker --- engine/worker/config.py | 3 +++ engine/worker/tasks.py | 1 + 2 files changed, 4 insertions(+) diff --git a/engine/worker/config.py b/engine/worker/config.py index e28bd12b..b82ded1c 100644 --- a/engine/worker/config.py +++ b/engine/worker/config.py @@ -23,6 +23,9 @@ class WorkerSettings(BaseSettings): # Policies directory POLICIES_DIR: str = os.path.join(os.path.dirname(__file__), "..", "policies") + # PowerShell service URL (optional - if set, uses HTTP instead of Docker) + POWERSHELL_SERVICE_URL: str | None = None + class Config: env_file = ".env" diff --git a/engine/worker/tasks.py b/engine/worker/tasks.py index e6c3b909..6be98454 100644 --- a/engine/worker/tasks.py +++ b/engine/worker/tasks.py @@ -338,6 +338,7 @@ async def _evaluate_control_async( tenant_id=credentials["tenant_id"], client_id=credentials["client_id"], client_secret=credentials["client_secret"], + service_url=settings.POWERSHELL_SERVICE_URL, ) else: # Entra and other collectors use Graph API From 9c1b2741ca957d22a65b4cdb165081c38ae9f517 Mon Sep 17 00:00:00 2001 From: David Hartley Date: Sun, 11 Jan 2026 16:32:24 +1100 Subject: [PATCH 082/111] Add support to the test collector for the powershell service --- engine/scripts/test_collector.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/engine/scripts/test_collector.py b/engine/scripts/test_collector.py index ae00495b..50639316 100644 --- a/engine/scripts/test_collector.py +++ b/engine/scripts/test_collector.py @@ -77,6 +77,7 @@ async def test_collector( collector_id: str, output_dir: Path | None = None, verbose: bool = False, + service_url: str | None = None, ) -> dict: """Run a collector and return/save results. @@ -84,6 +85,7 @@ async def test_collector( collector_id: The collector ID to run (e.g., 'entra.roles.cloud_only_admins') output_dir: Optional directory to save JSON output verbose: If True, print additional debug info + service_url: Optional PowerShell HTTP service URL Returns: Dict containing collector_id, timestamp, elapsed_seconds, and data @@ -102,12 +104,14 @@ async def test_collector( if verbose: print(f"Tenant ID: {tenant_id}") print(f"Client ID: {client_id}") + if service_url: + print(f"Using PowerShell service: {service_url}") print() # Create collector and appropriate client collector = get_collector(collector_id) if isinstance(collector, BasePowerShellCollector): - client = PowerShellClient(tenant_id, client_id, client_secret) + client = PowerShellClient(tenant_id, client_id, client_secret, service_url=service_url) else: client = GraphClient(tenant_id, client_id, client_secret) @@ -160,9 +164,14 @@ async def test_collector( return output -async def test_all_collectors(output_dir: Path | None = None) -> None: +async def test_all_collectors( + output_dir: Path | None = None, + service_url: str | None = None, +) -> None: """Test all registered collectors and report status.""" print("Testing all registered collectors...") + if service_url: + print(f"Using PowerShell service: {service_url}") print("=" * 60) tenant_id, client_id, client_secret = get_credentials() @@ -177,7 +186,7 @@ async def test_all_collectors(output_dir: Path | None = None) -> None: # Use appropriate client based on collector type if isinstance(collector, BasePowerShellCollector): if ps_client is None: - ps_client = PowerShellClient(tenant_id, client_id, client_secret) + ps_client = PowerShellClient(tenant_id, client_id, client_secret, service_url=service_url) client = ps_client else: client = graph_client @@ -254,6 +263,9 @@ def main() -> None: python -m scripts.test_collector -c entra.roles.cloud_only_admins -o ./samples/ python -m scripts.test_collector --all -o ./samples/ + # Use PowerShell HTTP service instead of local Docker + python -m scripts.test_collector -c exchange.organization.organization_config --use-service http://localhost:8001 + Environment Variables: M365_TENANT_ID Azure AD tenant ID M365_CLIENT_ID App registration client ID @@ -284,6 +296,13 @@ def main() -> None: action="store_true", help="Show verbose output including credentials (masked) and stack traces", ) + parser.add_argument( + "--use-service", + type=str, + default=None, + metavar="URL", + help="Use PowerShell HTTP service instead of Docker (e.g., http://localhost:8001)", + ) args = parser.parse_args() @@ -292,13 +311,13 @@ def main() -> None: return if args.all: - asyncio.run(test_all_collectors(args.output)) + asyncio.run(test_all_collectors(args.output, args.use_service)) return if not args.collector: parser.error("--collector is required (or use --list to see available collectors)") - asyncio.run(test_collector(args.collector, args.output, args.verbose)) + asyncio.run(test_collector(args.collector, args.output, args.verbose, args.use_service)) if __name__ == "__main__": From fb6dc045b46fa13b66a92125822852597957e151 Mon Sep 17 00:00:00 2001 From: David Hartley Date: Sun, 11 Jan 2026 16:32:48 +1100 Subject: [PATCH 083/111] Add support for the powershell service --- engine/collectors/powershell_client.py | 109 +++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 6 deletions(-) diff --git a/engine/collectors/powershell_client.py b/engine/collectors/powershell_client.py index 928b2bf4..cff9f7ed 100644 --- a/engine/collectors/powershell_client.py +++ b/engine/collectors/powershell_client.py @@ -2,7 +2,9 @@ This module provides connectivity to Microsoft 365 PowerShell modules using client secret authentication via MSAL access tokens. PowerShell execution -happens inside a Docker container for consistent behavior across environments. +can happen either: +- Inside a Docker container (local development with Docker Desktop) +- Via HTTP to a PowerShell service (Docker Compose / production) Supported modules: - ExchangeOnlineManagement (Exchange Online via -AccessToken) @@ -12,8 +14,8 @@ Authentication Flow: 1. Use MSAL ConfidentialClientApplication with client_id + client_secret 2. Acquire token for the appropriate scope -3. Pass token to Docker container via environment variable -4. Container runs PowerShell cmdlet and returns JSON +3. Pass token to Docker container (via env var) or HTTP service (via request body) +4. Container/service runs PowerShell cmdlet and returns JSON """ import json @@ -21,6 +23,7 @@ from pathlib import Path from typing import Any +import httpx from msal import ConfidentialClientApplication @@ -31,7 +34,7 @@ class PowerShellExecutionError(Exception): class PowerShellClient: - """Client for PowerShell-based M365 connections using Docker.""" + """Client for PowerShell-based M365 connections using Docker or HTTP service.""" # Service-specific scopes for token acquisition EXCHANGE_SCOPE = "https://outlook.office365.com/.default" @@ -40,17 +43,26 @@ class PowerShellClient: DOCKER_IMAGE = "autoaudit-powershell" - def __init__(self, tenant_id: str, client_id: str, client_secret: str): + def __init__( + self, + tenant_id: str, + client_id: str, + client_secret: str, + service_url: str | None = None, + ): """Initialize PowerShell client. Args: tenant_id: Azure AD tenant ID client_id: Application (client) ID client_secret: Client secret for authentication + service_url: Optional URL of PowerShell HTTP service (e.g., http://powershell-service:8001). + If provided, uses HTTP instead of spawning Docker containers. """ self.tenant_id = tenant_id self.client_id = client_id self.client_secret = client_secret + self.service_url = service_url self._msal_app = ConfidentialClientApplication( client_id=client_id, client_credential=client_secret, @@ -101,13 +113,98 @@ def _ensure_docker_image(self) -> None: self._image_checked = True async def run_cmdlet(self, module: str, cmdlet: str, **params: Any) -> dict[str, Any]: - """Execute a PowerShell cmdlet in Docker container. + """Execute a PowerShell cmdlet. + + Uses HTTP service if service_url is configured, otherwise spawns Docker container. Args: module: The PowerShell module (ExchangeOnline, Teams, Compliance) cmdlet: The cmdlet to run (e.g., Get-OrganizationConfig) **params: Parameters to pass to the cmdlet + Returns: + Dict containing cmdlet output. + """ + if self.service_url: + return await self._run_via_service(module, cmdlet, params) + else: + return await self._run_via_docker(module, cmdlet, params) + + async def _run_via_service( + self, module: str, cmdlet: str, params: dict[str, Any] + ) -> dict[str, Any]: + """Execute cmdlet via HTTP service. + + Args: + module: The PowerShell module + cmdlet: The cmdlet to run + params: Parameters for the cmdlet + + Returns: + Dict containing cmdlet output. + """ + # Acquire tokens + graph_token = None + if module == "Teams": + # Teams needs both Graph and Teams tokens + graph_result = self._msal_app.acquire_token_for_client( + scopes=["https://graph.microsoft.com/.default"] + ) + if "access_token" not in graph_result: + error_desc = graph_result.get("error_description", str(graph_result)) + raise RuntimeError(f"Graph token acquisition failed: {error_desc}") + graph_token = graph_result["access_token"] + + teams_result = self._msal_app.acquire_token_for_client( + scopes=[self.TEAMS_SCOPE] + ) + if "access_token" not in teams_result: + error_desc = teams_result.get("error_description", str(teams_result)) + raise RuntimeError(f"Teams token acquisition failed: {error_desc}") + token = teams_result["access_token"] + else: + # Exchange and Compliance use single token + scope = self._get_scope_for_module(module) + result = self._msal_app.acquire_token_for_client(scopes=[scope]) + if "access_token" not in result: + error_desc = result.get("error_description", str(result)) + raise RuntimeError(f"Token acquisition failed: {error_desc}") + token = result["access_token"] + + # Build request payload + payload = { + "module": module, + "cmdlet": cmdlet, + "params": params, + "tenant_id": self.tenant_id, + "token": token, + "graph_token": graph_token, + } + + # Call HTTP service + async with httpx.AsyncClient(timeout=120.0) as client: + response = await client.post( + f"{self.service_url}/execute", + json=payload, + ) + response.raise_for_status() + + result = response.json() + if not result.get("success"): + raise PowerShellExecutionError(result.get("error", "Unknown error")) + + return result.get("data") + + async def _run_via_docker( + self, module: str, cmdlet: str, params: dict[str, Any] + ) -> dict[str, Any]: + """Execute cmdlet by spawning Docker container. + + Args: + module: The PowerShell module + cmdlet: The cmdlet to run + params: Parameters for the cmdlet + Returns: Dict containing cmdlet output. """ From 4e4ff5ef5a93d42fd0fa93dca76168d891c77b67 Mon Sep 17 00:00:00 2001 From: David Hartley Date: Sun, 11 Jan 2026 16:33:05 +1100 Subject: [PATCH 084/111] Fix S6 collectors using the wrong cmdlets and update metadata --- .../organization/organization_config.py | 9 +- .../exchange/transport/transport_rules.py | 60 ++++++++-- .../v6.0.0/6.2.1_mail_forwarding_blocked.rego | 111 +++++++++++++++--- .../v6.0.0/6.5.4_smtp_auth.rego | 2 +- .../v6.0.0/6.5.5_direct_send.rego | 34 ++---- .../v6.0.0/metadata.json | 4 +- 6 files changed, 163 insertions(+), 57 deletions(-) diff --git a/engine/collectors/exchange/organization/organization_config.py b/engine/collectors/exchange/organization/organization_config.py index 562430d3..1b18d0ed 100644 --- a/engine/collectors/exchange/organization/organization_config.py +++ b/engine/collectors/exchange/organization/organization_config.py @@ -1,7 +1,7 @@ """Organization config collector. CIS Microsoft 365 Foundations Benchmark Controls: - v6.0.0: 1.3.6, 1.3.9, 6.1.1, 6.5.1, 6.5.2, 6.5.4, 6.5.5 + v6.0.0: 1.3.6, 1.3.9, 6.1.1, 6.5.1, 6.5.2, 6.5.5 Control Descriptions: 1.3.6 - Ensure Customer Lockbox is enabled @@ -9,8 +9,7 @@ 6.1.1 - Ensure AuditDisabled is set to False (mailbox auditing at org level) 6.5.1 - Ensure SMTP AUTH is disabled 6.5.2 - Ensure all senders are notified when mail is blocked - 6.5.4 - Ensure Direct Send is disabled - 6.5.5 - Ensure IMAP is disabled for all users + 6.5.5 - Ensure Direct Send submissions are rejected Connection Method: Exchange Online PowerShell (via Docker container) Authentication: Client secret via MSAL -> access token passed to -AccessToken parameter @@ -40,7 +39,7 @@ async def collect(self, client: PowerShellClient) -> dict[str, Any]: - customer_lockbox_enabled: Customer lockbox status (CIS 1.3.6) - oauth_enabled: OAuth authentication status - audit_disabled: Whether audit is disabled (CIS 6.1.1) - - smtp_client_auth_disabled: SMTP client auth status (CIS 6.5.1) + - reject_direct_send: Whether direct send is rejected (CIS 6.5.5) """ config = await client.run_cmdlet("ExchangeOnline", "Get-OrganizationConfig") @@ -49,5 +48,5 @@ async def collect(self, client: PowerShellClient) -> dict[str, Any]: "customer_lockbox_enabled": config.get("CustomerLockBoxEnabled"), "oauth_enabled": config.get("OAuth2ClientProfileEnabled"), "audit_disabled": config.get("AuditDisabled"), - "smtp_client_auth_disabled": config.get("SmtpClientAuthenticationDisabled"), + "reject_direct_send": config.get("RejectDirectSend"), } diff --git a/engine/collectors/exchange/transport/transport_rules.py b/engine/collectors/exchange/transport/transport_rules.py index c7396549..322293be 100644 --- a/engine/collectors/exchange/transport/transport_rules.py +++ b/engine/collectors/exchange/transport/transport_rules.py @@ -5,7 +5,7 @@ Connection Method: Exchange Online PowerShell (via Docker container) Authentication: Client secret via MSAL -> access token passed to -AccessToken parameter -Required Cmdlets: Get-TransportRule +Required Cmdlets: Get-TransportRule, Get-HostedOutboundSpamFilterPolicy Required Permissions: Exchange.ManageAsApp + Exchange role assignment """ @@ -20,16 +20,19 @@ class TransportRulesDataCollector(BasePowerShellCollector): This collector retrieves mail transport rules to check for mail forwarding rules and domain whitelisting configurations. + Also retrieves outbound spam filter policies for auto-forwarding settings. """ async def collect(self, client: PowerShellClient) -> dict[str, Any]: - """Collect transport rules data. + """Collect transport rules and outbound spam filter policy data. Returns: Dict containing: - transport_rules: List of transport rules - forwarding_rules: Rules that forward mail externally - whitelist_rules: Rules that whitelist domains + - outbound_spam_filter_policies: List of outbound spam filter policies + - auto_forwarding_blocked: Whether auto-forwarding is blocked in all policies """ rules = await client.run_cmdlet("ExchangeOnline", "Get-TransportRule") @@ -51,21 +54,58 @@ async def collect(self, client: PowerShellClient) -> dict[str, Any]: if r.get("RedirectMessageTo") or r.get("BlindCopyTo") ] - # Find rules that whitelist domains (SetSCL -1 or similar) - whitelist_rules = [ + # Find rules that whitelist domains (SetSCL -1 AND SenderDomainIs set) + # Per CIS 6.2.2: rule is non-compliant if it has BOTH properties together + whitelist_rules = [] + for r in rules: + # Convert SetSCL to int (may come as string from JSON) + set_scl_raw = r.get("SetSCL") + try: + set_scl = int(set_scl_raw) if set_scl_raw is not None else None + except (ValueError, TypeError): + set_scl = None + + sender_domain = r.get("SenderDomainIs") + + # Rule is a whitelist rule if it has BOTH SetSCL = -1 AND SenderDomainIs + if set_scl == -1 and sender_domain: + whitelist_rules.append({ + "name": r.get("Name"), + "state": r.get("State"), + "sender_domain": sender_domain, + "set_scl": set_scl, + }) + + # Get outbound spam filter policies for auto-forwarding check (CIS 6.2.1) + spam_policies = await client.run_cmdlet( + "ExchangeOnline", "Get-HostedOutboundSpamFilterPolicy" + ) + + # Handle None, single policy, or list + if spam_policies is None: + spam_policies = [] + elif isinstance(spam_policies, dict): + spam_policies = [spam_policies] + + # Extract auto-forwarding mode from each policy + outbound_policies = [ { - "name": r.get("Name"), - "state": r.get("State"), - "sender_domain": r.get("SenderDomainIs"), - "set_scl": r.get("SetSCL"), + "name": p.get("Name"), + "auto_forwarding_mode": p.get("AutoForwardingMode"), } - for r in rules - if r.get("SetSCL") == -1 or r.get("SenderDomainIs") + for p in spam_policies ] + # Check if all policies have AutoForwardingMode set to "Off" + auto_forwarding_blocked = all( + p.get("AutoForwardingMode") == "Off" for p in spam_policies + ) if spam_policies else False + return { "transport_rules": rules, "total_rules": len(rules), "forwarding_rules": forwarding_rules, "whitelist_rules": whitelist_rules, + "outbound_spam_filter_policies": outbound_policies, + "auto_forwarding_blocked": auto_forwarding_blocked, } diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.2.1_mail_forwarding_blocked.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.2.1_mail_forwarding_blocked.rego index aa9c266b..0d082a2b 100644 --- a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.2.1_mail_forwarding_blocked.rego +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.2.1_mail_forwarding_blocked.rego @@ -2,8 +2,9 @@ # title: Ensure all forms of mail forwarding are blocked and/or disabled # description: | # Transport rules that automatically forward or redirect mail to external -# recipients pose a significant data exfiltration risk. Ensure no transport -# rules exist that redirect or blind copy messages externally. +# recipients pose a significant data exfiltration risk. Additionally, +# outbound spam filter policies must have AutoForwardingMode set to Off. +# Rules that whitelist domains by setting SCL to -1 also pose security risks. # related_resources: # - ref: https://www.cisecurity.org/benchmark/microsoft_365 # description: CIS Microsoft 365 Foundations Benchmark @@ -19,36 +20,110 @@ package cis.microsoft_365_foundations.v6_0_0.control_6_2_1 -default result := {"compliant": false, "message": "Evaluation failed"} +import rego.v1 +default result := { + "compliant": false, + "message": "Evaluation failed: unable to retrieve outbound spam filter policy data", + "details": {} +} + +# Main evaluation rule result := output if { - forwarding_rules := input.forwarding_rules + # Outbound policies are required + outbound_policies := input.outbound_spam_filter_policies + auto_forwarding_blocked := input.auto_forwarding_blocked - # Filter to only enabled forwarding rules + # Get rules (default to empty array if missing) + forwarding_rules := get_array(input, "forwarding_rules") + whitelist_rules := get_array(input, "whitelist_rules") + + # Filter to only enabled rules enabled_forwarding_rules := [r | some r in forwarding_rules; r.state == "Enabled"] + enabled_whitelist_rules := [r | some r in whitelist_rules; r.state == "Enabled"] + + # Find policies where AutoForwardingMode is not Off + non_compliant_policies := [p | some p in outbound_policies; p.auto_forwarding_mode != "Off"] - # Compliant when no enabled forwarding rules exist - compliant := count(enabled_forwarding_rules) == 0 + # Check all conditions - compliant only if all are true + compliant := is_compliant(enabled_forwarding_rules, enabled_whitelist_rules, auto_forwarding_blocked) + + # Build message + msg := build_message(enabled_forwarding_rules, enabled_whitelist_rules, non_compliant_policies, auto_forwarding_blocked) + + # Build affected resources + affected := build_affected(enabled_forwarding_rules, enabled_whitelist_rules, non_compliant_policies) output := { "compliant": compliant, - "message": generate_message(enabled_forwarding_rules), - "affected_resources": [r.name | some r in enabled_forwarding_rules], + "message": msg, + "affected_resources": affected, "details": { - "total_transport_rules": input.total_rules, - "forwarding_rules_count": count(forwarding_rules), + "total_transport_rules": get_number(input, "total_rules"), "enabled_forwarding_rules": count(enabled_forwarding_rules), - "forwarding_rules": enabled_forwarding_rules + "enabled_whitelist_rules": count(enabled_whitelist_rules), + "forwarding_rules": enabled_forwarding_rules, + "whitelist_rules": enabled_whitelist_rules, + "outbound_spam_filter_policies": outbound_policies, + "auto_forwarding_blocked": auto_forwarding_blocked } } } -generate_message(enabled_forwarding_rules) := msg if { - count(enabled_forwarding_rules) == 0 - msg := "No enabled transport rules forward or redirect mail externally" +# Helper to get array with default +get_array(obj, key) := value if { + value := obj[key] +} else := [] + +# Helper to get number with default +get_number(obj, key) := value if { + value := obj[key] +} else := 0 + +# Helper to compute compliance - true only if all conditions met +is_compliant(fwd_rules, wl_rules, blocked) := true if { + count(fwd_rules) == 0 + count(wl_rules) == 0 + blocked == true +} else := false + +# Build message - all compliant +build_message(fwd, wl, policies, blocked) := "All forms of mail forwarding are blocked and no domain whitelist rules exist" if { + count(fwd) == 0 + count(wl) == 0 + blocked == true +} + +# Build message - has issues +build_message(fwd, wl, policies, blocked) := msg if { + issues := array.concat( + array.concat( + fwd_issues(fwd), + wl_issues(wl) + ), + policy_issues(policies, blocked) + ) + count(issues) > 0 + msg := concat("; ", issues) } -generate_message(enabled_forwarding_rules) := msg if { - count(enabled_forwarding_rules) > 0 - msg := sprintf("%d enabled transport rule(s) forward or redirect mail externally", [count(enabled_forwarding_rules)]) +# Issue helpers +fwd_issues(rules) := [sprintf("%d enabled forwarding rule(s)", [count(rules)])] if { + count(rules) > 0 +} else := [] + +wl_issues(rules) := [sprintf("%d enabled whitelist rule(s) with SCL=-1", [count(rules)])] if { + count(rules) > 0 +} else := [] + +policy_issues(policies, blocked) := [sprintf("AutoForwardingMode not Off in %d policy(ies)", [count(policies)])] if { + blocked == false +} else := [] + +# Build affected resources +build_affected(fwd, wl, policies) := resources if { + fwd_names := [sprintf("Forwarding rule: %s", [r.name]) | some r in fwd] + wl_names := [sprintf("Whitelist rule: %s (domains: %v)", [r.name, r.sender_domain]) | some r in wl] + policy_names := [sprintf("Outbound policy '%s' has AutoForwardingMode=%s", [p.name, p.auto_forwarding_mode]) | some p in policies] + resources := array.concat(array.concat(fwd_names, wl_names), policy_names) } diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.4_smtp_auth.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.4_smtp_auth.rego index 56db41f7..481723e6 100644 --- a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.4_smtp_auth.rego +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.4_smtp_auth.rego @@ -22,7 +22,7 @@ package cis.microsoft_365_foundations.v6_0_0.control_6_5_4 default result := {"compliant": false, "message": "Evaluation failed"} result := output if { - smtp_auth_disabled := input.smtp_client_auth_disabled + smtp_auth_disabled := input.smtp_client_authentication_disabled # Compliant when SMTP client authentication is disabled compliant := smtp_auth_disabled == true diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.5_direct_send.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.5_direct_send.rego index c22dd62a..f538f453 100644 --- a/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.5_direct_send.rego +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/6.5.5_direct_send.rego @@ -22,41 +22,33 @@ package cis.microsoft_365_foundations.v6_0_0.control_6_5_5 default result := {"compliant": false, "message": "Evaluation failed"} result := output if { - transport_config := input.transport_config - smtp_auth_disabled := input.smtp_client_authentication_disabled + reject_direct_send := input.reject_direct_send - # Direct Send is blocked when SMTP client authentication is disabled - # Additional check: ExternalSubmissionEnabled should be False for full compliance - external_submission := transport_config.ExternalDelayDsnEnabled - - # Compliant when SMTP client authentication is disabled - compliant := smtp_auth_disabled == true + # Compliant when RejectDirectSend is true + compliant := reject_direct_send == true output := { "compliant": compliant, - "message": generate_message(smtp_auth_disabled), + "message": generate_message(reject_direct_send), "affected_resources": generate_affected_resources(compliant), "details": { - "smtp_client_authentication_disabled": smtp_auth_disabled, - "transport_config": { - "external_delay_dsn_enabled": external_submission - } + "reject_direct_send": reject_direct_send } } } -generate_message(smtp_auth_disabled) := msg if { - smtp_auth_disabled == true - msg := "Direct Send submissions are blocked (SMTP client auth disabled)" +generate_message(reject_direct_send) := msg if { + reject_direct_send == true + msg := "Direct Send submissions are rejected" } -generate_message(smtp_auth_disabled) := msg if { - smtp_auth_disabled == false - msg := "Direct Send submissions are allowed (SMTP client auth enabled)" +generate_message(reject_direct_send) := msg if { + reject_direct_send == false + msg := "Direct Send submissions are allowed (RejectDirectSend is False)" } -generate_message(smtp_auth_disabled) := msg if { - smtp_auth_disabled == null +generate_message(reject_direct_send) := msg if { + reject_direct_send == null msg := "Unable to determine Direct Send status" } diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json b/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json index b2217c95..a91790bb 100644 --- a/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json @@ -1457,7 +1457,7 @@ "is_manual": false, "benchmark_audit_type": "Automated", "automation_status": "ready", - "data_collector_id": "exchange.organization.organization_config", + "data_collector_id": "exchange.organization.transport_config", "policy_file": "6.5.4_smtp_auth.rego", "requires_permissions": ["Exchange.Manage"], "notes": null @@ -1472,7 +1472,7 @@ "is_manual": false, "benchmark_audit_type": "Automated", "automation_status": "ready", - "data_collector_id": "exchange.organization.transport_config", + "data_collector_id": "exchange.organization.organization_config", "policy_file": "6.5.5_direct_send.rego", "requires_permissions": ["Exchange.Manage"], "notes": null From 7551bf94472a3044c03713b590ca540dd43e60ee Mon Sep 17 00:00:00 2001 From: David Hartley Date: Sun, 11 Jan 2026 16:33:22 +1100 Subject: [PATCH 085/111] Update docker compose, add support for the powershell service --- docker-compose.yml | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 5ab1bc09..67c0de76 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,12 +2,22 @@ # # Usage with profiles: # docker compose up # Start infrastructure only (db, redis, opa) -# docker compose --profile frontend-dev up # For frontend devs (starts backend-api + infrastructure) -# docker compose --profile backend-dev up # For backend devs (starts frontend + infrastructure) +# docker compose --profile frontend-dev up # For frontend devs (backend-api + infrastructure, run worker locally) +# docker compose --profile backend-dev up # For backend devs (frontend + infrastructure) +# docker compose --profile worker up # Add worker to any profile combination +# docker compose --profile powershell up # Add PowerShell service for Exchange/Teams cmdlets # docker compose --profile all up # Start all services # -# To run in detached mode, add -d flag: -# docker compose --profile frontend-dev up -d +# Common combinations: +# docker compose --profile frontend-dev up -d # Backend API + infra (worker runs locally) +# docker compose --profile frontend-dev --profile worker up -d # Backend API + worker + infra +# docker compose --profile frontend-dev --profile powershell up -d # Backend API + PowerShell service +# +# To run worker locally (for PowerShell/Exchange controls that need Docker access): +# cd engine && uv run celery -A worker.celery_app worker --loglevel=info +# +# To test collectors with PowerShell service: +# cd engine && uv run python -m scripts.test_collector -c exchange.organization.organization_config --use-service http://localhost:8001 services: # ============================================================================= @@ -117,6 +127,7 @@ services: restart: unless-stopped worker: + profiles: ["worker", "all"] build: context: ./engine dockerfile: Dockerfile @@ -133,6 +144,10 @@ services: # Required: Must match ENCRYPTION_KEY in backend-api for credential decryption - ENCRYPTION_KEY=Ps-HiS3ww5QzQPc_Mdu5-JyA_jCNbdFHMdiwWSlAfgM= + + # Optional: PowerShell service URL for Exchange/Teams cmdlets + # When set, worker uses HTTP service instead of spawning Docker containers + - POWERSHELL_SERVICE_URL=http://powershell-service:8001 volumes: - ./engine:/app/engine:ro - ./engine/policies:/app/policies:ro @@ -157,6 +172,21 @@ services: - "3000:3000" restart: unless-stopped + powershell-service: + profiles: ["powershell", "all"] + build: + context: ./engine/powershell + dockerfile: Dockerfile + container_name: autoaudit-powershell-service + ports: + - "8001:8001" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8001/health"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + volumes: postgres_data: redis_data: From 9abfeaf0cc9ff0ffa397f2edbc0efb1bd7a81d3a Mon Sep 17 00:00:00 2001 From: Romil Bijarnia Date: Tue, 13 Jan 2026 17:41:46 +0530 Subject: [PATCH 086/111] ui enhancements pt1 --- backend-api/app/api/v1/scans.py | 39 ++++++++- backend-api/app/models/compliance.py | 7 ++ backend-api/app/schemas/scan.py | 2 + engine/worker/db.py | 8 ++ engine/worker/tasks.py | 12 ++- frontend/src/pages/Scans/ScanDetailPage.css | 56 +++++++++++++ frontend/src/pages/Scans/ScanDetailPage.jsx | 91 +++++++++++++++++++-- frontend/src/pages/Scans/ScansPage.css | 18 ++++ frontend/src/pages/Scans/ScansPage.jsx | 68 ++++++++++++++- 9 files changed, 288 insertions(+), 13 deletions(-) diff --git a/backend-api/app/api/v1/scans.py b/backend-api/app/api/v1/scans.py index aa1fa722..1ae66691 100644 --- a/backend-api/app/api/v1/scans.py +++ b/backend-api/app/api/v1/scans.py @@ -1,7 +1,7 @@ """Scan API endpoints.""" from fastapi import APIRouter, Depends, HTTPException, status -from sqlalchemy import select +from sqlalchemy import delete, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload @@ -124,6 +124,7 @@ async def list_scans( """List scans for the current user.""" result = await db.execute( select(Scan) + .options(selectinload(Scan.m365_connection)) .where(Scan.user_id == current_user.id) .order_by(Scan.started_at.desc()) .limit(limit) @@ -141,7 +142,7 @@ async def get_scan( """Get scan details by ID including results.""" result = await db.execute( select(Scan) - .options(selectinload(Scan.results)) + .options(selectinload(Scan.results), selectinload(Scan.m365_connection)) .where(Scan.id == scan_id, Scan.user_id == current_user.id) ) scan = result.scalar_one_or_none() @@ -183,3 +184,37 @@ async def get_scan_results( results = await db.execute(query) return list(results.scalars().all()) + + +@router.delete("/{scan_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_scan( + scan_id: int, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_async_session), +) -> None: + """Delete a scan and its results. + + Only allow deleting scans that are no longer running to avoid race conditions + with in-flight Celery tasks updating scan results. + """ + result = await db.execute( + select(Scan).where(Scan.id == scan_id, Scan.user_id == current_user.id) + ) + scan = result.scalar_one_or_none() + if not scan: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Scan {scan_id} not found", + ) +#prevent this from the ui itself by hiding/disabling the button when the scanning is happening + if scan.status in {"pending", "running"}: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="Cannot delete a scan while it is pending or running", + ) + + # Explicitly delete child rows to avoid relying on DB-level cascades. + await db.execute(delete(ScanResult).where(ScanResult.scan_id == scan_id)) + await db.delete(scan) + await db.commit() + return None diff --git a/backend-api/app/models/compliance.py b/backend-api/app/models/compliance.py index 4b369d4b..178e928f 100644 --- a/backend-api/app/models/compliance.py +++ b/backend-api/app/models/compliance.py @@ -68,3 +68,10 @@ class Scan(Base): back_populates="scans" ) results: Mapped[list["ScanResult"]] = relationship(back_populates="scan") + + @property + def connection_name(self) -> str | None: + """Convenience field for API/UI display.""" + if self.m365_connection is not None: + return self.m365_connection.name + return None diff --git a/backend-api/app/schemas/scan.py b/backend-api/app/schemas/scan.py index 44bb0a08..a24fd57f 100644 --- a/backend-api/app/schemas/scan.py +++ b/backend-api/app/schemas/scan.py @@ -50,6 +50,7 @@ class ScanRead(BaseModel): id: int user_id: int m365_connection_id: int | None + connection_name: str | None = None azure_connection_id: int | None gcp_connection_id: int | None aws_connection_id: int | None @@ -77,6 +78,7 @@ class ScanListItem(BaseModel): id: int user_id: int m365_connection_id: int | None + connection_name: str | None = None framework: str benchmark: str version: str diff --git a/engine/worker/db.py b/engine/worker/db.py index 8e558680..68cbeb58 100644 --- a/engine/worker/db.py +++ b/engine/worker/db.py @@ -247,6 +247,14 @@ def increment_scan_error_count(session: Session, scan_id: int) -> None: ) +def increment_scan_skipped_count(session: Session, scan_id: int, amount: int = 1) -> None: + """Increment the skipped count for a scan.""" + session.execute( + text("UPDATE scan SET skipped_count = skipped_count + :amount WHERE id = :scan_id"), + {"scan_id": scan_id, "amount": amount}, + ) + + def update_scan_result( session: Session, result_id: int, diff --git a/engine/worker/tasks.py b/engine/worker/tasks.py index 6be98454..421f0c2b 100644 --- a/engine/worker/tasks.py +++ b/engine/worker/tasks.py @@ -15,6 +15,7 @@ update_scan_status, increment_scan_progress, increment_scan_error_count, + increment_scan_skipped_count, update_scan_result, finalize_scan_if_complete, ) @@ -159,6 +160,7 @@ def run_scan(scan_id: int) -> dict: status="skipped", message=f"Control {status}: {control.get('notes') or 'Not yet automatable'}", ) + increment_scan_skipped_count(session, scan_id) session.commit() skipped += 1 @@ -331,9 +333,13 @@ async def _evaluate_control_async( # Get collector collector = get_collector(collector_id) - # Determine client type based on collector_id prefix - # Exchange and Compliance collectors require PowerShell - if collector_id.startswith(("exchange.", "compliance.")): + # Determine client type based on collector_id prefix. + # + # Most Exchange and Compliance collectors require PowerShell, but a few Exchange + # collectors use Graph (e.g. domain metadata). + if collector_id.startswith(("exchange.", "compliance.")) and not collector_id.startswith( + "exchange.dns." + ): client = PowerShellClient( tenant_id=credentials["tenant_id"], client_id=credentials["client_id"], diff --git a/frontend/src/pages/Scans/ScanDetailPage.css b/frontend/src/pages/Scans/ScanDetailPage.css index 4c867d30..3ea75adf 100644 --- a/frontend/src/pages/Scans/ScanDetailPage.css +++ b/frontend/src/pages/Scans/ScanDetailPage.css @@ -115,6 +115,16 @@ font-size: 14px; } +.meta-value .meta-date { + font-weight: 600; +} + +.meta-value .meta-time { + margin-top: 2px; + color: var(--text-tertiary); + font-size: 12px; +} + .progress-card { background: var(--bg-secondary); border: 1px solid var(--border-color); @@ -148,6 +158,52 @@ margin: 0; } +.scan-progress-bar { + margin-top: 16px; +} + +.scan-progress-track { + width: 100%; + height: 10px; + border-radius: 999px; + background: rgba(100, 116, 139, 0.18); + overflow: hidden; + border: 1px solid rgba(148, 163, 184, 0.18); +} + +.scan-progress-fill { + height: 100%; + border-radius: 999px; + transition: width 0.25s ease; + background: linear-gradient(90deg, rgba(59, 130, 246, 0.9), rgba(59, 130, 246, 0.55)); +} + +.scan-progress-fill.completed { + background: linear-gradient(90deg, rgba(16, 185, 129, 0.9), rgba(16, 185, 129, 0.55)); +} + +.scan-progress-fill.failed { + background: linear-gradient(90deg, rgba(239, 68, 68, 0.9), rgba(239, 68, 68, 0.55)); +} + +.scan-progress-fill.pending { + background: linear-gradient(90deg, rgba(249, 115, 22, 0.9), rgba(249, 115, 22, 0.55)); +} + +.scan-progress-meta { + margin-top: 10px; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + color: var(--text-tertiary); + font-size: 12px; +} + +.scan-progress-sep { + color: var(--text-tertiary); +} + .stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); diff --git a/frontend/src/pages/Scans/ScanDetailPage.jsx b/frontend/src/pages/Scans/ScanDetailPage.jsx index b5a3839e..f9a92815 100644 --- a/frontend/src/pages/Scans/ScanDetailPage.jsx +++ b/frontend/src/pages/Scans/ScanDetailPage.jsx @@ -87,7 +87,53 @@ const ScanDetailPage = ({ sidebarWidth = 220, isDarkMode = true }) => { function formatDate(dateString) { if (!dateString) return '-'; - return new Date(dateString).toLocaleString(); + const d = new Date(dateString); + if (Number.isNaN(d.getTime())) return '-'; + // Force "DD Mon YYYY" and show timezone explicitly. + return d.toLocaleString('en-GB', { + day: '2-digit', + month: 'short', + year: 'numeric', + }); + } + + function formatTime(dateString) { + if (!dateString) return '-'; + const d = new Date(dateString); + if (Number.isNaN(d.getTime())) return '-'; + + function getLocalTimeZoneAbbr(dateObj) { + const longName = new Intl.DateTimeFormat('en-US', { + timeZoneName: 'long', + }).formatToParts(dateObj).find(p => p.type === 'timeZoneName')?.value; + + if (!longName) return ''; + if (longName ==='India Standard Time') return 'IST'; + if (longName === 'Coordinated Universal Time') return 'UTC'; + if (longName === 'Greenwich Mean Time') return 'GMT'; + if (longName === 'Universal Coordinated Time') return 'UTC'; + + const embedded = longName.match(/\b[A-Z]{2,6}\b/)?.[0]; + if (embedded) return embedded; + + const words = longName + .replace(/[^A-Za-z\s]/g, ' ') + .split(/\s+/) + .filter(Boolean); + const trimmed = words.filter((w, idx) => !(idx === words.length - 1 && w.toLowerCase() === 'time')); + const abbr = trimmed.map(w => w[0]).join('').toUpperCase(); + return abbr.length >= 2 ? abbr : ''; + } + + const timeCore = new Intl.DateTimeFormat('en-GB', { + hour: 'numeric', + minute: '2-digit', + second: '2-digit', + fractionalSecondDigits: 3, + hour12: true, + }).format(d); + const tzAbbr = getLocalTimeZoneAbbr(d); + return tzAbbr ? `${timeCore} ${tzAbbr}` : timeCore; } function getResultIcon(status) { @@ -175,6 +221,11 @@ const ScanDetailPage = ({ sidebarWidth = 220, isDarkMode = true }) => { errors: scan.error_count || 0, pending: (scan.total_controls || 0) - (scan.passed_count || 0) - (scan.failed_count || 0) - (scan.error_count || 0) - (scan.skipped_count || 0), }; + const done = summary.passed + summary.failed + summary.errors + (scan.skipped_count || 0); + const progressPercent = + summary.total > 0 + ? Math.min(100, Math.round((done / summary.total) * 100)) + : scan.status === 'completed' ? 100: 0; const results = scan.results || []; return ( @@ -212,15 +263,31 @@ const ScanDetailPage = ({ sidebarWidth = 220, isDarkMode = true }) => {
Connection - {scan.connection_name || '-'} + + {scan.connection_name || (scan.m365_connection_id ? `Connection #${scan.m365_connection_id}` : '-')} +
Started - {formatDate(scan.created_at)} +
+
{formatDate(scan.started_at || scan.created_at)}
+
{formatTime(scan.started_at || scan.created_at)}
+
Completed - {formatDate(scan.completed_at)} + {scan.finished_at || scan.completed_at ? ( +
+
{formatDate(scan.finished_at || scan.completed_at)}
+
{formatTime(scan.finished_at || scan.completed_at)}
+
+ ) : ( +
+
+ {(scan.status === 'pending' || scan.status === 'running') ? 'In progress' : '-'} +
+
+ )}
@@ -234,10 +301,24 @@ const ScanDetailPage = ({ sidebarWidth = 220, isDarkMode = true }) => {

{scan.status === 'pending' ? 'Waiting to start...' - : `Evaluating controls... ${summary.passed + summary.failed + summary.errors} of ${summary.total} complete`} + : `Evaluating controls... ${done} of ${summary.total} complete`}

+ +
+
+
+
+
+ {done}/{summary.total} controls + + {progressPercent}% +
+
)} diff --git a/frontend/src/pages/Scans/ScansPage.css b/frontend/src/pages/Scans/ScansPage.css index 830eb6d3..969763df 100644 --- a/frontend/src/pages/Scans/ScansPage.css +++ b/frontend/src/pages/Scans/ScansPage.css @@ -244,6 +244,24 @@ font-size: 13px; } +.datetime { + display: flex; + flex-direction: column; + gap: 2px; + line-height: 1.2; +} + +.datetime .date { + color: var(--text-primary); + font-weight: 600; + font-size: 13px; +} + +.datetime .time { + color: var(--text-tertiary); + font-size: 12px; +} + .results-summary .passed { color: #10b981; } diff --git a/frontend/src/pages/Scans/ScansPage.jsx b/frontend/src/pages/Scans/ScansPage.jsx index ace44d27..24a2b5e7 100644 --- a/frontend/src/pages/Scans/ScansPage.jsx +++ b/frontend/src/pages/Scans/ScansPage.jsx @@ -123,7 +123,58 @@ const ScansPage = ({ sidebarWidth = 220, isDarkMode = true }) => { function formatDate(dateString) { if (!dateString) return '-'; - return new Date(dateString).toLocaleString(); + const d = new Date(dateString); + if (Number.isNaN(d.getTime())) return '-'; + // Prettier "DD Mon YYYY" + "h:mm AM TZ" (2-line in UI). + const date = new Intl.DateTimeFormat('en-GB', { + day: '2-digit', + month: 'short', + year: 'numeric', + }).format(d); + + function getLocalTimeZoneAbbr(dateObj) { + // Use the *local* timezone name and derive an abbreviation (e.g., "India Standard Time" -> "IST"). + const longName = new Intl.DateTimeFormat('en-US', { + timeZoneName: 'long', + }).formatToParts(dateObj).find(p => p.type === 'timeZoneName')?.value; + console.log(longName); + if (!longName) return ''; + if (longName ==='India Standard Time') return 'IST';// Common canonical names that shouldn't be initialised. + if (longName === 'Coordinated Universal Time') return 'UTC'; + if (longName === 'Greenwich Mean Time') return 'GMT'; + if (longName === 'Universal Coordinated Time') return 'UTC'; + + // If a long name already contains a clean abbreviation, prefer it. + const embedded = longName.match(/\b[A-Z]{2,6}\b/)?.[0]; + if (embedded) return embedded; + + // Initialism from words, dropping common filler. + const stop = new Set(['time', 'standard', 'daylight']); + const words = longName + .replace(/[^A-Za-z\s]/g, ' ') + .split(/\s+/) + .filter(Boolean); + + // Keep 'Standard'/'Daylight' because they matter (EST vs EDT, AEST vs AEDT). + // Only drop the generic trailing "Time". + const trimmed = words.filter((w, idx) => !(idx === words.length - 1 && w.toLowerCase() === 'time')); + const abbr = trimmed.map(w => w[0]).join('').toUpperCase(); + + // Fallback to the long name if initialism is weirdly short. + return abbr.length >= 2 ? abbr : ''; + } + + const timeCore = new Intl.DateTimeFormat('en-GB', { + hour: 'numeric', + minute: '2-digit', + second: '2-digit', + fractionalSecondDigits: 3, + hour12: true, + }).format(d); + const tzAbbr = getLocalTimeZoneAbbr(d); + const time = tzAbbr ? `${timeCore} ${tzAbbr}` : timeCore; + + return { date, time }; } if (isLoading) { @@ -303,8 +354,19 @@ const ScansPage = ({ sidebarWidth = 220, isDarkMode = true }) => { {scan.version || ''} - {scan.connection_name || '-'} - {formatDate(scan.created_at)} + {scan.connection_name || (scan.m365_connection_id ? `Connection #${scan.m365_connection_id}` : '-')} + + {(() => { + const dt = formatDate(scan.started_at || scan.created_at); + if (dt === '-') return '-'; + return ( +
+
{dt.date}
+
{dt.time}
+
+ ); + })()} + {scan.status === 'completed' || scan.status === 'running' ? (
From ecd5e1b07c0c28a2db4c28773726d836a3aca0fb Mon Sep 17 00:00:00 2001 From: Romil Bijarnia Date: Tue, 13 Jan 2026 18:32:34 +0530 Subject: [PATCH 087/111] added 2 policies under the version 6 benchmark, controls have been tested --- .../v6.0.0/1.1.1_admin_cloud_only.rego | 67 +++++++++++++++++ .../v6.0.0/5.2.2.3_block_legacy_auth.rego | 72 +++++++++++++++++++ .../v6.0.0/metadata.json | 8 +-- 3 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/1.1.1_admin_cloud_only.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.2.3_block_legacy_auth.rego diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.1.1_admin_cloud_only.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.1.1_admin_cloud_only.rego new file mode 100644 index 00000000..6c601d4e --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.1.1_admin_cloud_only.rego @@ -0,0 +1,67 @@ +# METADATA +# title: Ensure Administrative accounts are cloud-only +# description: | +# Administrative accounts should not be synced from on-premises Active Directory. +# Cloud-only accounts reduce the attack surface by not being tied to on-premises +# infrastructure, preventing lateral movement from compromised on-prem environments. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-1.1.1 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: critical +# service: EntraID +# requires_permissions: +# - User.Read.All +# - RoleManagement.Read.Directory + +package cis.microsoft_365_foundations.v6_0_0.control_1_1_1 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Evaluation failed: unable to retrieve administrative account data", + "details": {} +} + +# Main evaluation rule +result := output if { + admin_accounts := get_array(input, "admin_accounts") + synced_admins := [a | some a in admin_accounts; a.on_premises_sync_enabled == true] + + compliant := count(synced_admins) == 0 + + msg := build_message(admin_accounts, synced_admins) + affected := [a.userPrincipalName | some a in synced_admins] + + output := { + "compliant": compliant, + "message": msg, + "affected_resources": affected, + "details": { + "total_admin_accounts": count(admin_accounts), + "synced_admin_count": count(synced_admins), + "cloud_only_admin_count": count(admin_accounts) - count(synced_admins), + "synced_admins": [{"userPrincipalName": a.userPrincipalName, "displayName": a.displayName, "admin_roles": a.admin_roles} | some a in synced_admins] + } + } +} + +# Helper to get array with default +get_array(obj, key) := value if { + value := obj[key] +} else := [] + +build_message(all_admins, synced_admins) := msg if { + count(synced_admins) == 0 + msg := sprintf("All %d administrative account(s) are cloud-only", [count(all_admins)]) +} + +build_message(all_admins, synced_admins) := msg if { + count(synced_admins) > 0 + msg := sprintf("%d of %d administrative account(s) are synced from on-premises AD", [count(synced_admins), count(all_admins)]) +} diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.2.3_block_legacy_auth.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.2.3_block_legacy_auth.rego new file mode 100644 index 00000000..ffbd4367 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.2.3_block_legacy_auth.rego @@ -0,0 +1,72 @@ +# METADATA +# title: Enable Conditional Access policies to block legacy authentication +# description: | +# Legacy authentication protocols do not support MFA and are commonly exploited +# in password spray and credential stuffing attacks. Implement Conditional Access +# policies to block these protocols for all users and all applications. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-5.2.2.3 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: high +# service: EntraID +# requires_permissions: +# - Policy.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_2_2_3 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Evaluation failed: unable to retrieve Conditional Access policy data", + "details": {} +} + +# Main evaluation rule +result := output if { + policies := get_array(input, "conditional_access_policies") + + blocking_policies := [p | some p in policies; is_legacy_auth_block_policy(p)] + compliant := count(blocking_policies) > 0 + + msg := build_message(compliant, blocking_policies) + affected := build_affected(compliant, blocking_policies) + + output := { + "compliant": compliant, + "message": msg, + "affected_resources": affected, + "details": { + "total_policies": count(policies), + "legacy_auth_block_policies": count(blocking_policies), + "blocking_policy_names": [p.display_name | some p in blocking_policies] + } + } +} + +# Helper to get array with default +get_array(obj, key) := value if { + value := obj[key] +} else := [] + +is_legacy_auth_block_policy(policy) if { + policy.state == "enabled" + policy.targets_all_users == true + policy.targets_all_apps == true + policy.blocks_legacy_auth == true + policy.grant_control == "block" +} + +build_message(true, blocking_policies) := msg if { + msg := sprintf("Found %d Conditional Access policy(ies) blocking legacy authentication", [count(blocking_policies)]) +} + +build_message(false, _) := "No Conditional Access policy found that blocks legacy authentication for all users and applications" + +build_affected(true, _) := [] +build_affected(false, _) := ["No legacy auth blocking policy configured"] diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json b/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json index 9abd1186..d2bc8216 100644 --- a/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json @@ -16,9 +16,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.roles.cloud_only_admins", - "policy_file": null, + "policy_file": "1.1.1_admin_cloud_only.rego", "requires_permissions": ["User.Read.All", "RoleManagement.Read.Directory"], "notes": null }, @@ -961,9 +961,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.conditional_access.legacy_auth_block", - "policy_file": null, + "policy_file": "5.2.2.3_block_legacy_auth.rego", "requires_permissions": ["Policy.Read.All"], "notes": null }, From 7c58a2591fa157ef5701f6408c0cdd4a14c6582b Mon Sep 17 00:00:00 2001 From: Romil Bijarnia Date: Thu, 15 Jan 2026 16:57:19 +0530 Subject: [PATCH 088/111] push v6 controls --- .../v6.0.0/1.1.3_global_admin_count.rego | 74 +++++++++++++++++++ .../v6.0.0/1.3.1_password_expiration.rego | 63 ++++++++++++++++ .../1.3.4_user_owned_apps_restricted.rego | 51 +++++++++++++ ....5_forms_internal_phishing_protection.rego | 56 ++++++++++++++ .../v6.0.0/2.1.9_DKIM_is_enabled.rego | 8 +- .../v6.0.0/metadata.json | 16 ++-- 6 files changed, 257 insertions(+), 11 deletions(-) create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/1.1.3_global_admin_count.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/1.3.1_password_expiration.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/1.3.4_user_owned_apps_restricted.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/1.3.5_forms_internal_phishing_protection.rego diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.1.3_global_admin_count.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.1.3_global_admin_count.rego new file mode 100644 index 00000000..dd143bdf --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.1.3_global_admin_count.rego @@ -0,0 +1,74 @@ +# METADATA +# title: Ensure that between two and four global admins are designated +# description: | +# Maintain between 2-4 global administrators to ensure operational continuity +# while minimizing attack surface. Having fewer than 2 creates a single point +# of failure, while having more than 4 unnecessarily expands the attack surface +# and increases the risk of credential compromise. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-1.1.3 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: high +# service: EntraID +# requires_permissions: +# - RoleManagement.Read.Directory +# - User.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_1_1_3 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Evaluation failed: unable to retrieve Global Administrator role membership", + "details": {} +} + +default compliant := false + +compliant if { + admin_count := input.global_admin_count + admin_count >= 2 + admin_count <= 4 +} + +result := output if { + admin_count := input.global_admin_count + admins := get_array(input, "global_admins") + + output := { + "compliant": compliant, + "message": generate_message(admin_count), + "affected_resources": admins, + "details": { + "global_admin_count": admin_count, + "recommended_min": 2, + "recommended_max": 4 + } + } +} + +generate_message(admin_count) := msg if { + admin_count < 2 + msg := sprintf("Only %d global admin(s) found. Minimum 2 recommended for continuity.", [admin_count]) +} + +generate_message(admin_count) := msg if { + admin_count > 4 + msg := sprintf("%d global admins found. Maximum 4 recommended to minimize attack surface.", [admin_count]) +} + +generate_message(admin_count) := msg if { + admin_count >= 2 + admin_count <= 4 + msg := sprintf("%d global admins configured (within recommended range of 2-4)", [admin_count]) +} + +get_array(obj, key) := value if { + value := obj[key] +} else := [] diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.3.1_password_expiration.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.3.1_password_expiration.rego new file mode 100644 index 00000000..0a3908b7 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.3.1_password_expiration.rego @@ -0,0 +1,63 @@ +# METADATA +# title: Ensure the 'Password expiration policy' is set to 'Set passwords to never expire (recommended)' +# description: | +# Configure password policies to never expire. NIST and Microsoft recommend +# this approach when MFA is enabled, as forced password rotation leads to +# weaker passwords without providing security benefits. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-1.3.1 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Domain.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_1_3_1 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Evaluation failed: unable to retrieve domain password policy data", + "details": {} +} + +# 2147483647 = "never expires" (max int32) +never_expires := 2147483647 + +result := output if { + domains := get_array(input, "domains") + managed_domains := [d | some d in domains; d.is_managed == true] + non_compliant := [d | some d in managed_domains; d.password_validity_days != never_expires] + + output := { + "compliant": count(non_compliant) == 0, + "message": generate_message(managed_domains, non_compliant), + "affected_resources": [d.domain_name | some d in non_compliant], + "details": { + "total_managed_domains": count(managed_domains), + "compliant_domains": count(managed_domains) - count(non_compliant), + "non_compliant_domains": count(non_compliant), + "domains": [{"name": d.domain_name, "password_validity_days": d.password_validity_days} | some d in managed_domains] + } + } +} + +generate_message(managed_domains, non_compliant) := msg if { + count(non_compliant) == 0 + msg := sprintf("All %d managed domain(s) have password expiration disabled", [count(managed_domains)]) +} + +generate_message(managed_domains, non_compliant) := msg if { + count(non_compliant) > 0 + msg := sprintf("%d of %d managed domain(s) have password expiration enabled", [count(non_compliant), count(managed_domains)]) +} + +get_array(obj, key) := value if { + value := obj[key] +} else := [] diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.3.4_user_owned_apps_restricted.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.3.4_user_owned_apps_restricted.rego new file mode 100644 index 00000000..0b0308a9 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.3.4_user_owned_apps_restricted.rego @@ -0,0 +1,51 @@ +# METADATA +# title: Ensure 'User owned apps and services' is restricted +# description: | +# Allowing users to own apps and services increases the risk of unauthorized +# application integrations and data exposure. Restrict this capability so that +# only approved administrators can enable and manage user apps and services. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-1.3.4 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Policy.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_1_3_4 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Evaluation failed: unable to retrieve Microsoft 365 Apps and Services settings", + "details": {} +} + +result := output if { + enabled := input.user_owned_apps_enabled + + output := { + # CIS intent: restricted => disabled + "compliant": enabled == false, + "message": generate_message(enabled), + "affected_resources": generate_affected(enabled), + "details": { + "user_owned_apps_enabled": enabled, + "is_office_store_enabled": input.is_office_store_enabled + } + } +} + +generate_message(true) := "User owned apps and services are not restricted (enabled)" +generate_message(false) := "User owned apps and services are restricted (disabled)" +generate_message(null) := "Unable to determine whether user owned apps and services are restricted" + +generate_affected(false) := [] +generate_affected(true) := ["User owned apps and services are enabled"] +generate_affected(null) := ["User owned apps and services setting unknown"] diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.3.5_forms_internal_phishing_protection.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.3.5_forms_internal_phishing_protection.rego new file mode 100644 index 00000000..17342aa2 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.3.5_forms_internal_phishing_protection.rego @@ -0,0 +1,56 @@ +# METADATA +# title: Ensure internal phishing protection for Forms is enabled +# description: | +# Internal phishing protection helps prevent misuse of Microsoft Forms for +# phishing within the organization. Enable internal phishing protection to +# reduce the risk of credential harvesting and social engineering attacks. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-1.3.5 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: Forms +# requires_permissions: +# - OrgSettings-Forms.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_1_3_5 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Evaluation failed: unable to retrieve Microsoft Forms settings", + "details": {} +} + +result := output if { + enabled := input.internal_phishing_protection_enabled + + output := { + "compliant": enabled == true, + "message": generate_message(enabled), + "affected_resources": generate_affected(enabled), + "details": { + "internal_phishing_protection_enabled": enabled, + "external_sharing_enabled": input.external_sharing_enabled, + "external_send_form_enabled": input.external_send_form_enabled, + "external_share_collaborating_enabled": input.external_share_collaborating_enabled, + "external_share_template_enabled": input.external_share_template_enabled, + "external_share_result_enabled": input.external_share_result_enabled, + "bing_search_enabled": input.bing_search_enabled, + "record_identity_by_default_enabled": input.record_identity_by_default_enabled + } + } +} + +generate_message(true) := "Microsoft Forms internal phishing protection is enabled" +generate_message(false) := "Microsoft Forms internal phishing protection is not enabled" +generate_message(null) := "Unable to determine Microsoft Forms internal phishing protection setting" + +generate_affected(true) := [] +generate_affected(false) := ["Microsoft Forms internal phishing protection is disabled"] +generate_affected(null) := ["Microsoft Forms internal phishing protection setting unknown"] diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.9_DKIM_is_enabled.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.9_DKIM_is_enabled.rego index a3752027..67528edc 100644 --- a/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.9_DKIM_is_enabled.rego +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/2.1.9_DKIM_is_enabled.rego @@ -1,9 +1,9 @@ # METADATA # title: Ensure that DKIM is enabled for all Exchange Online Domains # description: | -# DKIM is one of the trio of Authentication methods (SPF, DKIM and DMARC) that help -# prevent attackers from sending messages that look like they come from your domain. -# +# DKIM is one of the trio of authentication methods (SPF, DKIM, and DMARC) that help +# prevent attackers from sending messages that look like they come from your domain. +# # related_resources: # - ref: https://www.cisecurity.org/benchmark/microsoft_365 # description: CIS Microsoft 365 Foundations Benchmark @@ -19,6 +19,8 @@ package cis.microsoft_365_foundations.v6_0_0.control_2_1_9 +import rego.v1 + default result := {"compliant": false, "message": "Evaluation failed"} # Compute dkim_enabled from per-domain lists diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json b/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json index d2bc8216..92ef0fc9 100644 --- a/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json @@ -46,9 +46,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.roles.privileged_roles", - "policy_file": null, + "policy_file": "1.1.3_global_admin_count.rego", "requires_permissions": ["RoleManagement.Read.Directory", "User.Read.All"], "notes": null }, @@ -106,9 +106,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.domains.password_policy", - "policy_file": null, + "policy_file": "1.3.1_password_expiration.rego", "requires_permissions": ["Domain.Read.All"], "notes": null }, @@ -151,9 +151,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.applications.apps_and_services_settings", - "policy_file": null, + "policy_file": "1.3.4_user_owned_apps_restricted.rego", "requires_permissions": ["Policy.Read.All"], "notes": null }, @@ -166,9 +166,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.applications.forms_settings", - "policy_file": null, + "policy_file": "1.3.5_forms_internal_phishing_protection.rego", "requires_permissions": ["Policy.Read.All"], "notes": null }, From bf0722db0a87506d5513809e3a57a58b7e7ec179 Mon Sep 17 00:00:00 2001 From: Romil Bijarnia Date: Tue, 13 Jan 2026 17:41:46 +0530 Subject: [PATCH 089/111] ui enhancements pt1 --- backend-api/app/api/v1/scans.py | 39 ++++++++- backend-api/app/models/compliance.py | 7 ++ backend-api/app/schemas/scan.py | 2 + engine/worker/db.py | 8 ++ engine/worker/tasks.py | 12 ++- frontend/src/pages/Scans/ScanDetailPage.css | 56 +++++++++++++ frontend/src/pages/Scans/ScanDetailPage.jsx | 91 +++++++++++++++++++-- frontend/src/pages/Scans/ScansPage.css | 18 ++++ frontend/src/pages/Scans/ScansPage.jsx | 68 ++++++++++++++- 9 files changed, 288 insertions(+), 13 deletions(-) diff --git a/backend-api/app/api/v1/scans.py b/backend-api/app/api/v1/scans.py index aa1fa722..1ae66691 100644 --- a/backend-api/app/api/v1/scans.py +++ b/backend-api/app/api/v1/scans.py @@ -1,7 +1,7 @@ """Scan API endpoints.""" from fastapi import APIRouter, Depends, HTTPException, status -from sqlalchemy import select +from sqlalchemy import delete, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload @@ -124,6 +124,7 @@ async def list_scans( """List scans for the current user.""" result = await db.execute( select(Scan) + .options(selectinload(Scan.m365_connection)) .where(Scan.user_id == current_user.id) .order_by(Scan.started_at.desc()) .limit(limit) @@ -141,7 +142,7 @@ async def get_scan( """Get scan details by ID including results.""" result = await db.execute( select(Scan) - .options(selectinload(Scan.results)) + .options(selectinload(Scan.results), selectinload(Scan.m365_connection)) .where(Scan.id == scan_id, Scan.user_id == current_user.id) ) scan = result.scalar_one_or_none() @@ -183,3 +184,37 @@ async def get_scan_results( results = await db.execute(query) return list(results.scalars().all()) + + +@router.delete("/{scan_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_scan( + scan_id: int, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_async_session), +) -> None: + """Delete a scan and its results. + + Only allow deleting scans that are no longer running to avoid race conditions + with in-flight Celery tasks updating scan results. + """ + result = await db.execute( + select(Scan).where(Scan.id == scan_id, Scan.user_id == current_user.id) + ) + scan = result.scalar_one_or_none() + if not scan: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Scan {scan_id} not found", + ) +#prevent this from the ui itself by hiding/disabling the button when the scanning is happening + if scan.status in {"pending", "running"}: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="Cannot delete a scan while it is pending or running", + ) + + # Explicitly delete child rows to avoid relying on DB-level cascades. + await db.execute(delete(ScanResult).where(ScanResult.scan_id == scan_id)) + await db.delete(scan) + await db.commit() + return None diff --git a/backend-api/app/models/compliance.py b/backend-api/app/models/compliance.py index 4b369d4b..178e928f 100644 --- a/backend-api/app/models/compliance.py +++ b/backend-api/app/models/compliance.py @@ -68,3 +68,10 @@ class Scan(Base): back_populates="scans" ) results: Mapped[list["ScanResult"]] = relationship(back_populates="scan") + + @property + def connection_name(self) -> str | None: + """Convenience field for API/UI display.""" + if self.m365_connection is not None: + return self.m365_connection.name + return None diff --git a/backend-api/app/schemas/scan.py b/backend-api/app/schemas/scan.py index 44bb0a08..a24fd57f 100644 --- a/backend-api/app/schemas/scan.py +++ b/backend-api/app/schemas/scan.py @@ -50,6 +50,7 @@ class ScanRead(BaseModel): id: int user_id: int m365_connection_id: int | None + connection_name: str | None = None azure_connection_id: int | None gcp_connection_id: int | None aws_connection_id: int | None @@ -77,6 +78,7 @@ class ScanListItem(BaseModel): id: int user_id: int m365_connection_id: int | None + connection_name: str | None = None framework: str benchmark: str version: str diff --git a/engine/worker/db.py b/engine/worker/db.py index 8e558680..68cbeb58 100644 --- a/engine/worker/db.py +++ b/engine/worker/db.py @@ -247,6 +247,14 @@ def increment_scan_error_count(session: Session, scan_id: int) -> None: ) +def increment_scan_skipped_count(session: Session, scan_id: int, amount: int = 1) -> None: + """Increment the skipped count for a scan.""" + session.execute( + text("UPDATE scan SET skipped_count = skipped_count + :amount WHERE id = :scan_id"), + {"scan_id": scan_id, "amount": amount}, + ) + + def update_scan_result( session: Session, result_id: int, diff --git a/engine/worker/tasks.py b/engine/worker/tasks.py index 6be98454..421f0c2b 100644 --- a/engine/worker/tasks.py +++ b/engine/worker/tasks.py @@ -15,6 +15,7 @@ update_scan_status, increment_scan_progress, increment_scan_error_count, + increment_scan_skipped_count, update_scan_result, finalize_scan_if_complete, ) @@ -159,6 +160,7 @@ def run_scan(scan_id: int) -> dict: status="skipped", message=f"Control {status}: {control.get('notes') or 'Not yet automatable'}", ) + increment_scan_skipped_count(session, scan_id) session.commit() skipped += 1 @@ -331,9 +333,13 @@ async def _evaluate_control_async( # Get collector collector = get_collector(collector_id) - # Determine client type based on collector_id prefix - # Exchange and Compliance collectors require PowerShell - if collector_id.startswith(("exchange.", "compliance.")): + # Determine client type based on collector_id prefix. + # + # Most Exchange and Compliance collectors require PowerShell, but a few Exchange + # collectors use Graph (e.g. domain metadata). + if collector_id.startswith(("exchange.", "compliance.")) and not collector_id.startswith( + "exchange.dns." + ): client = PowerShellClient( tenant_id=credentials["tenant_id"], client_id=credentials["client_id"], diff --git a/frontend/src/pages/Scans/ScanDetailPage.css b/frontend/src/pages/Scans/ScanDetailPage.css index 4c867d30..3ea75adf 100644 --- a/frontend/src/pages/Scans/ScanDetailPage.css +++ b/frontend/src/pages/Scans/ScanDetailPage.css @@ -115,6 +115,16 @@ font-size: 14px; } +.meta-value .meta-date { + font-weight: 600; +} + +.meta-value .meta-time { + margin-top: 2px; + color: var(--text-tertiary); + font-size: 12px; +} + .progress-card { background: var(--bg-secondary); border: 1px solid var(--border-color); @@ -148,6 +158,52 @@ margin: 0; } +.scan-progress-bar { + margin-top: 16px; +} + +.scan-progress-track { + width: 100%; + height: 10px; + border-radius: 999px; + background: rgba(100, 116, 139, 0.18); + overflow: hidden; + border: 1px solid rgba(148, 163, 184, 0.18); +} + +.scan-progress-fill { + height: 100%; + border-radius: 999px; + transition: width 0.25s ease; + background: linear-gradient(90deg, rgba(59, 130, 246, 0.9), rgba(59, 130, 246, 0.55)); +} + +.scan-progress-fill.completed { + background: linear-gradient(90deg, rgba(16, 185, 129, 0.9), rgba(16, 185, 129, 0.55)); +} + +.scan-progress-fill.failed { + background: linear-gradient(90deg, rgba(239, 68, 68, 0.9), rgba(239, 68, 68, 0.55)); +} + +.scan-progress-fill.pending { + background: linear-gradient(90deg, rgba(249, 115, 22, 0.9), rgba(249, 115, 22, 0.55)); +} + +.scan-progress-meta { + margin-top: 10px; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + color: var(--text-tertiary); + font-size: 12px; +} + +.scan-progress-sep { + color: var(--text-tertiary); +} + .stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); diff --git a/frontend/src/pages/Scans/ScanDetailPage.jsx b/frontend/src/pages/Scans/ScanDetailPage.jsx index b5a3839e..f9a92815 100644 --- a/frontend/src/pages/Scans/ScanDetailPage.jsx +++ b/frontend/src/pages/Scans/ScanDetailPage.jsx @@ -87,7 +87,53 @@ const ScanDetailPage = ({ sidebarWidth = 220, isDarkMode = true }) => { function formatDate(dateString) { if (!dateString) return '-'; - return new Date(dateString).toLocaleString(); + const d = new Date(dateString); + if (Number.isNaN(d.getTime())) return '-'; + // Force "DD Mon YYYY" and show timezone explicitly. + return d.toLocaleString('en-GB', { + day: '2-digit', + month: 'short', + year: 'numeric', + }); + } + + function formatTime(dateString) { + if (!dateString) return '-'; + const d = new Date(dateString); + if (Number.isNaN(d.getTime())) return '-'; + + function getLocalTimeZoneAbbr(dateObj) { + const longName = new Intl.DateTimeFormat('en-US', { + timeZoneName: 'long', + }).formatToParts(dateObj).find(p => p.type === 'timeZoneName')?.value; + + if (!longName) return ''; + if (longName ==='India Standard Time') return 'IST'; + if (longName === 'Coordinated Universal Time') return 'UTC'; + if (longName === 'Greenwich Mean Time') return 'GMT'; + if (longName === 'Universal Coordinated Time') return 'UTC'; + + const embedded = longName.match(/\b[A-Z]{2,6}\b/)?.[0]; + if (embedded) return embedded; + + const words = longName + .replace(/[^A-Za-z\s]/g, ' ') + .split(/\s+/) + .filter(Boolean); + const trimmed = words.filter((w, idx) => !(idx === words.length - 1 && w.toLowerCase() === 'time')); + const abbr = trimmed.map(w => w[0]).join('').toUpperCase(); + return abbr.length >= 2 ? abbr : ''; + } + + const timeCore = new Intl.DateTimeFormat('en-GB', { + hour: 'numeric', + minute: '2-digit', + second: '2-digit', + fractionalSecondDigits: 3, + hour12: true, + }).format(d); + const tzAbbr = getLocalTimeZoneAbbr(d); + return tzAbbr ? `${timeCore} ${tzAbbr}` : timeCore; } function getResultIcon(status) { @@ -175,6 +221,11 @@ const ScanDetailPage = ({ sidebarWidth = 220, isDarkMode = true }) => { errors: scan.error_count || 0, pending: (scan.total_controls || 0) - (scan.passed_count || 0) - (scan.failed_count || 0) - (scan.error_count || 0) - (scan.skipped_count || 0), }; + const done = summary.passed + summary.failed + summary.errors + (scan.skipped_count || 0); + const progressPercent = + summary.total > 0 + ? Math.min(100, Math.round((done / summary.total) * 100)) + : scan.status === 'completed' ? 100: 0; const results = scan.results || []; return ( @@ -212,15 +263,31 @@ const ScanDetailPage = ({ sidebarWidth = 220, isDarkMode = true }) => {
Connection - {scan.connection_name || '-'} + + {scan.connection_name || (scan.m365_connection_id ? `Connection #${scan.m365_connection_id}` : '-')} +
Started - {formatDate(scan.created_at)} +
+
{formatDate(scan.started_at || scan.created_at)}
+
{formatTime(scan.started_at || scan.created_at)}
+
Completed - {formatDate(scan.completed_at)} + {scan.finished_at || scan.completed_at ? ( +
+
{formatDate(scan.finished_at || scan.completed_at)}
+
{formatTime(scan.finished_at || scan.completed_at)}
+
+ ) : ( +
+
+ {(scan.status === 'pending' || scan.status === 'running') ? 'In progress' : '-'} +
+
+ )}
@@ -234,10 +301,24 @@ const ScanDetailPage = ({ sidebarWidth = 220, isDarkMode = true }) => {

{scan.status === 'pending' ? 'Waiting to start...' - : `Evaluating controls... ${summary.passed + summary.failed + summary.errors} of ${summary.total} complete`} + : `Evaluating controls... ${done} of ${summary.total} complete`}

+ +
+
+
+
+
+ {done}/{summary.total} controls + + {progressPercent}% +
+
)} diff --git a/frontend/src/pages/Scans/ScansPage.css b/frontend/src/pages/Scans/ScansPage.css index 830eb6d3..969763df 100644 --- a/frontend/src/pages/Scans/ScansPage.css +++ b/frontend/src/pages/Scans/ScansPage.css @@ -244,6 +244,24 @@ font-size: 13px; } +.datetime { + display: flex; + flex-direction: column; + gap: 2px; + line-height: 1.2; +} + +.datetime .date { + color: var(--text-primary); + font-weight: 600; + font-size: 13px; +} + +.datetime .time { + color: var(--text-tertiary); + font-size: 12px; +} + .results-summary .passed { color: #10b981; } diff --git a/frontend/src/pages/Scans/ScansPage.jsx b/frontend/src/pages/Scans/ScansPage.jsx index ace44d27..24a2b5e7 100644 --- a/frontend/src/pages/Scans/ScansPage.jsx +++ b/frontend/src/pages/Scans/ScansPage.jsx @@ -123,7 +123,58 @@ const ScansPage = ({ sidebarWidth = 220, isDarkMode = true }) => { function formatDate(dateString) { if (!dateString) return '-'; - return new Date(dateString).toLocaleString(); + const d = new Date(dateString); + if (Number.isNaN(d.getTime())) return '-'; + // Prettier "DD Mon YYYY" + "h:mm AM TZ" (2-line in UI). + const date = new Intl.DateTimeFormat('en-GB', { + day: '2-digit', + month: 'short', + year: 'numeric', + }).format(d); + + function getLocalTimeZoneAbbr(dateObj) { + // Use the *local* timezone name and derive an abbreviation (e.g., "India Standard Time" -> "IST"). + const longName = new Intl.DateTimeFormat('en-US', { + timeZoneName: 'long', + }).formatToParts(dateObj).find(p => p.type === 'timeZoneName')?.value; + console.log(longName); + if (!longName) return ''; + if (longName ==='India Standard Time') return 'IST';// Common canonical names that shouldn't be initialised. + if (longName === 'Coordinated Universal Time') return 'UTC'; + if (longName === 'Greenwich Mean Time') return 'GMT'; + if (longName === 'Universal Coordinated Time') return 'UTC'; + + // If a long name already contains a clean abbreviation, prefer it. + const embedded = longName.match(/\b[A-Z]{2,6}\b/)?.[0]; + if (embedded) return embedded; + + // Initialism from words, dropping common filler. + const stop = new Set(['time', 'standard', 'daylight']); + const words = longName + .replace(/[^A-Za-z\s]/g, ' ') + .split(/\s+/) + .filter(Boolean); + + // Keep 'Standard'/'Daylight' because they matter (EST vs EDT, AEST vs AEDT). + // Only drop the generic trailing "Time". + const trimmed = words.filter((w, idx) => !(idx === words.length - 1 && w.toLowerCase() === 'time')); + const abbr = trimmed.map(w => w[0]).join('').toUpperCase(); + + // Fallback to the long name if initialism is weirdly short. + return abbr.length >= 2 ? abbr : ''; + } + + const timeCore = new Intl.DateTimeFormat('en-GB', { + hour: 'numeric', + minute: '2-digit', + second: '2-digit', + fractionalSecondDigits: 3, + hour12: true, + }).format(d); + const tzAbbr = getLocalTimeZoneAbbr(d); + const time = tzAbbr ? `${timeCore} ${tzAbbr}` : timeCore; + + return { date, time }; } if (isLoading) { @@ -303,8 +354,19 @@ const ScansPage = ({ sidebarWidth = 220, isDarkMode = true }) => { {scan.version || ''} - {scan.connection_name || '-'} - {formatDate(scan.created_at)} + {scan.connection_name || (scan.m365_connection_id ? `Connection #${scan.m365_connection_id}` : '-')} + + {(() => { + const dt = formatDate(scan.started_at || scan.created_at); + if (dt === '-') return '-'; + return ( +
+
{dt.date}
+
{dt.time}
+
+ ); + })()} + {scan.status === 'completed' || scan.status === 'running' ? (
From c6aa0cd559f53f7a807238b0d06f18e9b71a914a Mon Sep 17 00:00:00 2001 From: Romil Bijarnia Date: Sat, 17 Jan 2026 10:30:49 +0530 Subject: [PATCH 090/111] hide skipped controls, put them in order --- frontend/src/pages/Scans/ScanDetailPage.jsx | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/Scans/ScanDetailPage.jsx b/frontend/src/pages/Scans/ScanDetailPage.jsx index f9a92815..95b52808 100644 --- a/frontend/src/pages/Scans/ScanDetailPage.jsx +++ b/frontend/src/pages/Scans/ScanDetailPage.jsx @@ -15,6 +15,21 @@ import { useAuth } from '../../context/AuthContext'; import { getScan } from '../../api/client'; import './ScanDetailPage.css'; +function compareControlIdAscending(a, b) { + const aId = (a?.control_id || '').toString(); + const bId = (b?.control_id || '').toString(); + const aParts = aId.split('.').map(s => Number.parseInt(s, 10)); + const bParts = bId.split('.').map(s => Number.parseInt(s, 10)); + + const len = Math.max(aParts.length, bParts.length); + for (let i = 0; i < len; i++) { + const av = Number.isFinite(aParts[i]) ? aParts[i] : -1; + const bv = Number.isFinite(bParts[i]) ? bParts[i] : -1; + if (av !== bv) return av - bv; + } + return aId.localeCompare(bId); +} + const ScanDetailPage = ({ sidebarWidth = 220, isDarkMode = true }) => { const { scanId } = useParams(); const navigate = useNavigate(); @@ -226,7 +241,10 @@ const ScanDetailPage = ({ sidebarWidth = 220, isDarkMode = true }) => { summary.total > 0 ? Math.min(100, Math.round((done / summary.total) * 100)) : scan.status === 'completed' ? 100: 0; - const results = scan.results || []; + const results = (scan.results || []) + .filter(r => (r?.status || '').toLowerCase() !== 'skipped') + .slice() + .sort(compareControlIdAscending); return (
Date: Sat, 17 Jan 2026 11:06:48 +0530 Subject: [PATCH 091/111] Eliminate delete functionality for now, fix the timezone to AEST using uitility functions in the client side --- backend-api/app/api/v1/scans.py | 36 +-------- frontend/src/pages/Scans/ScanDetailPage.jsx | 48 +---------- frontend/src/pages/Scans/ScansPage.jsx | 55 +------------ frontend/src/utils/helpers.js | 89 +++++++++++++++++++++ 4 files changed, 95 insertions(+), 133 deletions(-) diff --git a/backend-api/app/api/v1/scans.py b/backend-api/app/api/v1/scans.py index 1ae66691..5282d064 100644 --- a/backend-api/app/api/v1/scans.py +++ b/backend-api/app/api/v1/scans.py @@ -1,7 +1,7 @@ """Scan API endpoints.""" from fastapi import APIRouter, Depends, HTTPException, status -from sqlalchemy import delete, select +from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload @@ -184,37 +184,3 @@ async def get_scan_results( results = await db.execute(query) return list(results.scalars().all()) - - -@router.delete("/{scan_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_scan( - scan_id: int, - current_user: User = Depends(get_current_user), - db: AsyncSession = Depends(get_async_session), -) -> None: - """Delete a scan and its results. - - Only allow deleting scans that are no longer running to avoid race conditions - with in-flight Celery tasks updating scan results. - """ - result = await db.execute( - select(Scan).where(Scan.id == scan_id, Scan.user_id == current_user.id) - ) - scan = result.scalar_one_or_none() - if not scan: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Scan {scan_id} not found", - ) -#prevent this from the ui itself by hiding/disabling the button when the scanning is happening - if scan.status in {"pending", "running"}: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail="Cannot delete a scan while it is pending or running", - ) - - # Explicitly delete child rows to avoid relying on DB-level cascades. - await db.execute(delete(ScanResult).where(ScanResult.scan_id == scan_id)) - await db.delete(scan) - await db.commit() - return None diff --git a/frontend/src/pages/Scans/ScanDetailPage.jsx b/frontend/src/pages/Scans/ScanDetailPage.jsx index 95b52808..e0a4b8be 100644 --- a/frontend/src/pages/Scans/ScanDetailPage.jsx +++ b/frontend/src/pages/Scans/ScanDetailPage.jsx @@ -13,6 +13,7 @@ import { } from 'lucide-react'; import { useAuth } from '../../context/AuthContext'; import { getScan } from '../../api/client'; +import { formatDateAEST, formatTimeAEST } from '../../utils/helpers'; import './ScanDetailPage.css'; function compareControlIdAscending(a, b) { @@ -101,54 +102,11 @@ const ScanDetailPage = ({ sidebarWidth = 220, isDarkMode = true }) => { } function formatDate(dateString) { - if (!dateString) return '-'; - const d = new Date(dateString); - if (Number.isNaN(d.getTime())) return '-'; - // Force "DD Mon YYYY" and show timezone explicitly. - return d.toLocaleString('en-GB', { - day: '2-digit', - month: 'short', - year: 'numeric', - }); + return formatDateAEST(dateString); } function formatTime(dateString) { - if (!dateString) return '-'; - const d = new Date(dateString); - if (Number.isNaN(d.getTime())) return '-'; - - function getLocalTimeZoneAbbr(dateObj) { - const longName = new Intl.DateTimeFormat('en-US', { - timeZoneName: 'long', - }).formatToParts(dateObj).find(p => p.type === 'timeZoneName')?.value; - - if (!longName) return ''; - if (longName ==='India Standard Time') return 'IST'; - if (longName === 'Coordinated Universal Time') return 'UTC'; - if (longName === 'Greenwich Mean Time') return 'GMT'; - if (longName === 'Universal Coordinated Time') return 'UTC'; - - const embedded = longName.match(/\b[A-Z]{2,6}\b/)?.[0]; - if (embedded) return embedded; - - const words = longName - .replace(/[^A-Za-z\s]/g, ' ') - .split(/\s+/) - .filter(Boolean); - const trimmed = words.filter((w, idx) => !(idx === words.length - 1 && w.toLowerCase() === 'time')); - const abbr = trimmed.map(w => w[0]).join('').toUpperCase(); - return abbr.length >= 2 ? abbr : ''; - } - - const timeCore = new Intl.DateTimeFormat('en-GB', { - hour: 'numeric', - minute: '2-digit', - second: '2-digit', - fractionalSecondDigits: 3, - hour12: true, - }).format(d); - const tzAbbr = getLocalTimeZoneAbbr(d); - return tzAbbr ? `${timeCore} ${tzAbbr}` : timeCore; + return formatTimeAEST(dateString); } function getResultIcon(status) { diff --git a/frontend/src/pages/Scans/ScansPage.jsx b/frontend/src/pages/Scans/ScansPage.jsx index 24a2b5e7..b6a97981 100644 --- a/frontend/src/pages/Scans/ScansPage.jsx +++ b/frontend/src/pages/Scans/ScansPage.jsx @@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { Search, Plus, CheckCircle, XCircle, Clock, Loader2, AlertCircle, PlayCircle } from 'lucide-react'; import { useAuth } from '../../context/AuthContext'; import { getScans, getConnections, getBenchmarks, createScan } from '../../api/client'; +import { formatDateTimePartsAEST } from '../../utils/helpers'; import './ScansPage.css'; const ScansPage = ({ sidebarWidth = 220, isDarkMode = true }) => { @@ -122,59 +123,7 @@ const ScansPage = ({ sidebarWidth = 220, isDarkMode = true }) => { } function formatDate(dateString) { - if (!dateString) return '-'; - const d = new Date(dateString); - if (Number.isNaN(d.getTime())) return '-'; - // Prettier "DD Mon YYYY" + "h:mm AM TZ" (2-line in UI). - const date = new Intl.DateTimeFormat('en-GB', { - day: '2-digit', - month: 'short', - year: 'numeric', - }).format(d); - - function getLocalTimeZoneAbbr(dateObj) { - // Use the *local* timezone name and derive an abbreviation (e.g., "India Standard Time" -> "IST"). - const longName = new Intl.DateTimeFormat('en-US', { - timeZoneName: 'long', - }).formatToParts(dateObj).find(p => p.type === 'timeZoneName')?.value; - console.log(longName); - if (!longName) return ''; - if (longName ==='India Standard Time') return 'IST';// Common canonical names that shouldn't be initialised. - if (longName === 'Coordinated Universal Time') return 'UTC'; - if (longName === 'Greenwich Mean Time') return 'GMT'; - if (longName === 'Universal Coordinated Time') return 'UTC'; - - // If a long name already contains a clean abbreviation, prefer it. - const embedded = longName.match(/\b[A-Z]{2,6}\b/)?.[0]; - if (embedded) return embedded; - - // Initialism from words, dropping common filler. - const stop = new Set(['time', 'standard', 'daylight']); - const words = longName - .replace(/[^A-Za-z\s]/g, ' ') - .split(/\s+/) - .filter(Boolean); - - // Keep 'Standard'/'Daylight' because they matter (EST vs EDT, AEST vs AEDT). - // Only drop the generic trailing "Time". - const trimmed = words.filter((w, idx) => !(idx === words.length - 1 && w.toLowerCase() === 'time')); - const abbr = trimmed.map(w => w[0]).join('').toUpperCase(); - - // Fallback to the long name if initialism is weirdly short. - return abbr.length >= 2 ? abbr : ''; - } - - const timeCore = new Intl.DateTimeFormat('en-GB', { - hour: 'numeric', - minute: '2-digit', - second: '2-digit', - fractionalSecondDigits: 3, - hour12: true, - }).format(d); - const tzAbbr = getLocalTimeZoneAbbr(d); - const time = tzAbbr ? `${timeCore} ${tzAbbr}` : timeCore; - - return { date, time }; + return formatDateTimePartsAEST(dateString); } if (isLoading) { diff --git a/frontend/src/utils/helpers.js b/frontend/src/utils/helpers.js index bee7f473..efc74385 100644 --- a/frontend/src/utils/helpers.js +++ b/frontend/src/utils/helpers.js @@ -11,6 +11,95 @@ export const formatDate = (date) => { return new Date(date).toLocaleDateString(undefined, options); }; +// Standardized GMT/UTC date+time formatting for consistent display across users/machines. +// - Date: "DD Mon YYYY" +// - Time: "h:mm:ss.SSS AM GMT" +function parseDateAssumingUTC(value) { + if (!value) return null; + if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value; + + // If backend returns an ISO string WITHOUT timezone (e.g. "2026-01-17T05:09:13"), + // browsers may treat it as local time. We treat such values as UTC to keep + // timestamps globally consistent. + if (typeof value === 'string') { + const s = value.trim(); + const hasTz = /([zZ]|[+-]\d{2}:\d{2})$/.test(s); + const looksIso = /^\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}(:\d{2}(\.\d{1,6})?)?$/.test(s); + const normalized = !hasTz && looksIso ? `${s.replace(' ', 'T')}Z` : s; + const d = new Date(normalized); + return Number.isNaN(d.getTime()) ? null : d; + } + + const d = new Date(value); + return Number.isNaN(d.getTime()) ? null : d; +} + +export const formatDateGMT = (dateString) => { + const d = parseDateAssumingUTC(dateString); + if (!d) return '-'; + return new Intl.DateTimeFormat('en-GB', { + day: '2-digit', + month: 'short', + year: 'numeric', + timeZone: 'UTC', + }).format(d); +}; + +export const formatTimeGMT = (dateString) => { + const d = parseDateAssumingUTC(dateString); + if (!d) return '-'; + const timeCore = new Intl.DateTimeFormat('en-GB', { + hour: 'numeric', + minute: '2-digit', + second: '2-digit', + fractionalSecondDigits: 3, + hour12: true, + timeZone: 'UTC', + }).format(d); + return `${timeCore} GMT`; +}; + +export const formatDateTimePartsGMT = (dateString) => { + const date = formatDateGMT(dateString); + if (date === '-') return { date: '-', time: '-' }; + return { date, time: formatTimeGMT(dateString) }; +}; + +// AEST display (fixed UTC+10). We intentionally use Australia/Brisbane because it +// does not observe DST; label remains "AEST" year-round as requested. +const AEST_IANA_TZ = 'Australia/Brisbane'; + +export const formatDateAEST = (dateString) => { + const d = parseDateAssumingUTC(dateString); + if (!d) return '-'; + return new Intl.DateTimeFormat('en-GB', { + day: '2-digit', + month: 'short', + year: 'numeric', + timeZone: AEST_IANA_TZ, + }).format(d); +}; + +export const formatTimeAEST = (dateString) => { + const d = parseDateAssumingUTC(dateString); + if (!d) return '-'; + const timeCore = new Intl.DateTimeFormat('en-GB', { + hour: 'numeric', + minute: '2-digit', + second: '2-digit', + fractionalSecondDigits: 3, + hour12: true, + timeZone: AEST_IANA_TZ, + }).format(d); + return `${timeCore} AEST`; +}; + +export const formatDateTimePartsAEST = (dateString) => { + const date = formatDateAEST(dateString); + if (date === '-') return { date: '-', time: '-' }; + return { date, time: formatTimeAEST(dateString) }; +}; + // Simple utility for string truncation export const truncateString = (str, maxLength) => { if (str.length <= maxLength) return str; From 1ab25d4e1c30f29346ebde26e1e047226cafc27c Mon Sep 17 00:00:00 2001 From: Romil Bijarnia Date: Sat, 17 Jan 2026 12:26:50 +0530 Subject: [PATCH 092/111] Add more controls for v6, configurable using the GraphClient --- .../1.2.1_no_unmanaged_public_groups.rego | 46 ++++++++ ..._mark_unmanaged_devices_not_compliant.rego | 55 ++++++++++ .../4.2_block_personal_device_enrollment.rego | 44 ++++++++ ...2.2_block_third_party_integrated_apps.rego | 42 ++++++++ .../5.1.2.3_restrict_tenant_creation.rego | 42 ++++++++ .../5.1.3.1_dynamic_guest_group_exists.rego | 73 +++++++++++++ ...5.1.3.2_block_security_group_creation.rego | 42 ++++++++ .../v6.0.0/5.1.4.1_restrict_device_join.rego | 52 +++++++++ ...1.4.6_restrict_bitlocker_key_recovery.rego | 42 ++++++++ .../5.1.5.1_block_user_app_consent.rego | 71 ++++++++++++ ....1.5.2_admin_consent_workflow_enabled.rego | 50 +++++++++ ...restrict_collaboration_invite_domains.rego | 57 ++++++++++ .../5.1.6.2_restrict_guest_user_access.rego | 54 ++++++++++ .../5.1.6.3_limit_guest_invitations.rego | 44 ++++++++ .../5.2.3.1_mfa_fatigue_protection.rego | 49 +++++++++ ...2.3.2_custom_banned_passwords_enabled.rego | 46 ++++++++ ....3_enable_on_prem_password_protection.rego | 48 +++++++++ .../5.2.3.4_all_members_mfa_capable.rego | 53 +++++++++ .../5.2.3.5_disable_weak_auth_methods.rego | 50 +++++++++ .../5.2.3.6_enable_system_preferred_mfa.rego | 56 ++++++++++ .../v6.0.0/5.2.3.7_disable_email_otp.rego | 46 ++++++++ .../v6.0.0/5.3.1_pim_enabled.rego | 45 ++++++++ ...5.3.2_guest_access_reviews_configured.rego | 46 ++++++++ ...ileged_role_access_reviews_configured.rego | 67 ++++++++++++ ...5.3.4_ga_activation_requires_approval.rego | 50 +++++++++ ....3.5_pra_activation_requires_approval.rego | 47 ++++++++ .../v6.0.0/metadata.json | 102 +++++++++--------- 27 files changed, 1368 insertions(+), 51 deletions(-) create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/1.2.1_no_unmanaged_public_groups.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/4.1_mark_unmanaged_devices_not_compliant.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/4.2_block_personal_device_enrollment.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.2.2_block_third_party_integrated_apps.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.2.3_restrict_tenant_creation.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.3.1_dynamic_guest_group_exists.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.3.2_block_security_group_creation.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.4.1_restrict_device_join.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.4.6_restrict_bitlocker_key_recovery.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.5.1_block_user_app_consent.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.5.2_admin_consent_workflow_enabled.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.6.1_restrict_collaboration_invite_domains.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.6.2_restrict_guest_user_access.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.6.3_limit_guest_invitations.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.1_mfa_fatigue_protection.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.2_custom_banned_passwords_enabled.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.3_enable_on_prem_password_protection.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.4_all_members_mfa_capable.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.5_disable_weak_auth_methods.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.6_enable_system_preferred_mfa.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.7_disable_email_otp.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.1_pim_enabled.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.2_guest_access_reviews_configured.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.3_privileged_role_access_reviews_configured.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.4_ga_activation_requires_approval.rego create mode 100644 engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.5_pra_activation_requires_approval.rego diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.2.1_no_unmanaged_public_groups.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.2.1_no_unmanaged_public_groups.rego new file mode 100644 index 00000000..cd2a041b --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/1.2.1_no_unmanaged_public_groups.rego @@ -0,0 +1,46 @@ +# METADATA +# title: Ensure that only organizationally managed/approved public groups exist +# description: Public groups should be reviewed and managed by the organization. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-1.2.1 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Group.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_1_2_1 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine public group configuration", + "details": {}, +} + +publics := object.get(input, "public_groups", []) if { true } + +compliant_value := true if { count(publics) == 0 } else := false if { true } + +msg := "No public groups exist" if { compliant_value } else := sprintf("%d public group(s) exist and require organizational approval", [count(publics)]) if { true } + +# This is a best-effort automated check. +# We treat any Public groups as requiring review and fail the control so it remains actionable. +result := output if { + output := { + "compliant": compliant_value, + "message": msg, + "details": { + "total_groups": input.total_groups, + "public_groups_count": count(publics), + "public_groups": [{"id": g.id, "displayName": g.displayName} | some g in publics], + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/4.1_mark_unmanaged_devices_not_compliant.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/4.1_mark_unmanaged_devices_not_compliant.rego new file mode 100644 index 00000000..0355b0f7 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/4.1_mark_unmanaged_devices_not_compliant.rego @@ -0,0 +1,55 @@ +# METADATA +# title: Ensure devices without a compliance policy are marked 'not compliant' +# description: Mark devices without compliance policy as non-compliant. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/mem/intune/protect/actions-for-noncompliance +# description: Intune actions for noncompliance (conceptual) +# custom: +# control_id: CIS-4.1 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: Intune +# requires_permissions: +# - DeviceManagementConfiguration.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_4_1 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine Intune compliance defaults", + "details": {}, +} + +# Compliant when both secureByDefault and scheduled noncompliance actions are enabled. +compliant if { + input.secure_by_default == true + input.is_scheduled_action_enabled == true +} + +compliant_value := true if { compliant } else := false if { true } + +msg := "Devices without a compliance policy are treated as not compliant (secureByDefault & scheduled actions enabled)" if { compliant } else := "Intune compliance defaults are not sufficiently strict" if { true } + +# Heuristic based on tenant settings: +# - secureByDefault should be true +# - isScheduledActionEnabled should be true (scheduled actions for noncompliance) +result := output if { + secure_by_default := input.secure_by_default + scheduled := input.is_scheduled_action_enabled + + output := { + "compliant": compliant_value, + "message": msg, + "details": { + "secure_by_default": secure_by_default, + "is_scheduled_action_enabled": scheduled, + "device_compliance_checkin_threshold_days": input.device_compliance_on_boarded, + "compliance_policy_summaries_count": count(input.compliance_policy_summaries), + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/4.2_block_personal_device_enrollment.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/4.2_block_personal_device_enrollment.rego new file mode 100644 index 00000000..90a87100 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/4.2_block_personal_device_enrollment.rego @@ -0,0 +1,44 @@ +# METADATA +# title: Ensure device enrollment for personally owned devices is blocked by default +# description: Block personal device enrollment by default. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-4.2 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: Intune +# requires_permissions: +# - DeviceManagementServiceConfig.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_4_2 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine personal device enrollment restriction", + "details": {}, +} + +compliant_value := true if { input.personal_devices_blocked == true } else := false if { true } + +msg := "Personal device enrollment is blocked by default" if { input.personal_devices_blocked == true } else := "Personal device enrollment is not blocked by default" if { input.personal_devices_blocked == false } else := "Unable to determine personal device enrollment restriction" if { true } + +result := output if { + blocked := input.personal_devices_blocked + + output := { + "compliant": compliant_value, + "message": msg, + "details": { + "personal_devices_blocked": blocked, + "total_configurations": input.total_configurations, + "platform_restrictions_count": count(input.platform_restrictions), + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.2.2_block_third_party_integrated_apps.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.2.2_block_third_party_integrated_apps.rego new file mode 100644 index 00000000..b979d936 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.2.2_block_third_party_integrated_apps.rego @@ -0,0 +1,42 @@ +# METADATA +# title: Ensure third party integrated applications are not allowed +# description: Block third-party application registration. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-5.1.2.2 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Policy.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_1_2_2 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine allowedToCreateApps", + "details": {}, +} + +compliant_value := true if { input.allowed_to_create_apps == false } else := false if { true } + +msg := "Third party integrated applications are not allowed (allowedToCreateApps=false)" if { input.allowed_to_create_apps == false } else := "Third party integrated applications are allowed (allowedToCreateApps=true)" if { input.allowed_to_create_apps == true } else := "Unable to determine allowedToCreateApps" if { true } + +result := out if { + value := input.allowed_to_create_apps + + out := { + "compliant": compliant_value, + "message": msg, + "details": { + "allowed_to_create_apps": value, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.2.3_restrict_tenant_creation.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.2.3_restrict_tenant_creation.rego new file mode 100644 index 00000000..cdb8a239 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.2.3_restrict_tenant_creation.rego @@ -0,0 +1,42 @@ +# METADATA +# title: Ensure 'Restrict non-admin users from creating tenants' is set to 'Yes' +# description: Prevent non-admin users from creating tenants. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-5.1.2.3 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Policy.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_1_2_3 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine allowedToCreateTenants", + "details": {}, +} + +compliant_value := true if { input.allowed_to_create_tenants == false } else := false if { true } + +msg := "Non-admin users cannot create tenants (allowedToCreateTenants=false)" if { input.allowed_to_create_tenants == false } else := "Non-admin users can create tenants (allowedToCreateTenants=true)" if { input.allowed_to_create_tenants == true } else := "Unable to determine allowedToCreateTenants" if { true } + +result := out if { + value := input.allowed_to_create_tenants + + out := { + "compliant": compliant_value, + "message": msg, + "details": { + "allowed_to_create_tenants": value, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.3.1_dynamic_guest_group_exists.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.3.1_dynamic_guest_group_exists.rego new file mode 100644 index 00000000..61e7f992 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.3.1_dynamic_guest_group_exists.rego @@ -0,0 +1,73 @@ +# METADATA +# title: Ensure a dynamic group for guest users is created +# description: Create a dynamic group to manage guest users. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/entra/identity/users/groups-dynamic-membership +# description: Dynamic membership rules (conceptual) +# custom: +# control_id: CIS-5.1.3.1 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Group.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_1_3_1 + +import rego.v1 + +default result := { + "compliant": false, + "message": "No dynamic guest user group detected", + "details": {}, +} + +# Look for dynamic membership rules referencing Guest user type. +is_guest_rule(rule) if { + lower(rule) != "" + contains(lower(rule), "guest") +} + +compliant_value := true if { + dyn := input.dynamic_groups + matching := [g | + some g in dyn + rule := g.membershipRule + rule != null + is_guest_rule(rule) + ] + count(matching) > 0 +} else := false if { true } + +msg := sprintf("Found %d dynamic group(s) targeting guest users", [count(matching)]) if { + dyn := input.dynamic_groups + matching := [g | + some g in dyn + rule := g.membershipRule + rule != null + is_guest_rule(rule) + ] + count(matching) > 0 +} else := "No dynamic guest user group detected" if { true } + +result := output if { + dyn := input.dynamic_groups + matching := [g | + some g in dyn + rule := g.membershipRule + rule != null + is_guest_rule(rule) + ] + + output := { + "compliant": compliant_value, + "message": msg, + "details": { + "dynamic_groups_count": input.dynamic_groups_count, + "matching_groups": [{"id": g.id, "displayName": g.displayName, "membershipRule": g.membershipRule} | some g in matching], + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.3.2_block_security_group_creation.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.3.2_block_security_group_creation.rego new file mode 100644 index 00000000..9582d044 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.3.2_block_security_group_creation.rego @@ -0,0 +1,42 @@ +# METADATA +# title: Ensure users cannot create security groups +# description: Prevent users from creating security groups. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-5.1.3.2 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Policy.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_1_3_2 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine allowedToCreateSecurityGroups", + "details": {}, +} + +compliant_value := true if { input.allowed_to_create_security_groups == false } else := false if { true } + +msg := "Users cannot create security groups (allowedToCreateSecurityGroups=false)" if { input.allowed_to_create_security_groups == false } else := "Users can create security groups (allowedToCreateSecurityGroups=true)" if { input.allowed_to_create_security_groups == true } else := "Unable to determine allowedToCreateSecurityGroups" if { true } + +result := out if { + value := input.allowed_to_create_security_groups + + out := { + "compliant": compliant_value, + "message": msg, + "details": { + "allowed_to_create_security_groups": value, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.4.1_restrict_device_join.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.4.1_restrict_device_join.rego new file mode 100644 index 00000000..3e797df1 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.4.1_restrict_device_join.rego @@ -0,0 +1,52 @@ +# METADATA +# title: Ensure the ability to join devices to Entra is restricted +# description: Restrict device join to authorized users. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/entra/identity/devices/device-join +# description: Device join settings (conceptual) +# custom: +# control_id: CIS-5.1.4.1 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Policy.Read.DeviceConfiguration + +package cis.microsoft_365_foundations.v6_0_0.control_5_1_4_1 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine device join restrictions", + "details": {}, +} + +compliant if { + input.azure_ad_join_allowed_users != null + input.azure_ad_join_allowed_users != "all" +} + +compliant_value := true if { compliant } else := false if { true } + +msg := "Device join is restricted to selected users/groups" if { compliant } else := "Device join appears to be allowed broadly (allowedUsers=all or missing)" if { true } + +result := output if { + users := input.azure_ad_join_allowed_users + groups := input.azure_ad_join_allowed_groups + + # Compliant when join is not broadly allowed to everyone. + # We treat "all" / empty / null as non-compliant. + output := { + "compliant": compliant_value, + "message": msg, + "details": { + "allowed_users": users, + "allowed_groups": groups, + "user_device_quota": input.user_device_quota, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.4.6_restrict_bitlocker_key_recovery.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.4.6_restrict_bitlocker_key_recovery.rego new file mode 100644 index 00000000..9417d80c --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.4.6_restrict_bitlocker_key_recovery.rego @@ -0,0 +1,42 @@ +# METADATA +# title: Ensure users are restricted from recovering BitLocker keys +# description: Restrict BitLocker key recovery to admins. +# related_resources: +# - ref: https://www.cisecurity.org/benchmark/microsoft_365 +# description: CIS Microsoft 365 Foundations Benchmark +# custom: +# control_id: CIS-5.1.4.6 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Policy.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_1_4_6 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine allowedToReadBitlockerKeysForOwnedDevice", + "details": {}, +} + +compliant_value := true if { input.allowed_to_read_bitlocker_keys_for_owned_device == false } else := false if { true } + +msg := "Users are restricted from recovering BitLocker keys (allowedToReadBitlockerKeysForOwnedDevice=false)" if { input.allowed_to_read_bitlocker_keys_for_owned_device == false } else := "Users can recover BitLocker keys (allowedToReadBitlockerKeysForOwnedDevice=true)" if { input.allowed_to_read_bitlocker_keys_for_owned_device == true } else := "Unable to determine allowedToReadBitlockerKeysForOwnedDevice" if { true } + +result := out if { + value := input.allowed_to_read_bitlocker_keys_for_owned_device + + out := { + "compliant": compliant_value, + "message": msg, + "details": { + "allowed_to_read_bitlocker_keys_for_owned_device": value, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.5.1_block_user_app_consent.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.5.1_block_user_app_consent.rego new file mode 100644 index 00000000..cc9cd59b --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.5.1_block_user_app_consent.rego @@ -0,0 +1,71 @@ +# METADATA +# title: Ensure user consent to apps accessing company data on their behalf is not allowed +# description: Block user consent to applications for company data. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-user-consent +# description: Configure user consent settings in Entra ID +# custom: +# control_id: CIS-5.1.5.1 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: high +# service: EntraID +# requires_permissions: +# - Policy.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_1_5_1 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine user consent settings (permissionGrantPoliciesAssigned)", + "details": {}, +} + +compliant if { + perms := input.default_user_role_permissions + assigned := perms.permissionGrantPoliciesAssigned + assigned == [] +} + +compliant_value := true if { compliant } else := false if { true } + +assigned_count := count(assigned) if { + perms := input.default_user_role_permissions + assigned := perms.permissionGrantPoliciesAssigned + assigned != null +} else := null if { true } + +msg := "User consent to apps is disabled (permissionGrantPoliciesAssigned is empty)" if { + compliant +} else := sprintf( + "User consent to apps is enabled/restricted (permissionGrantPoliciesAssigned has %d entries)", + [assigned_count], +) if { + perms := input.default_user_role_permissions + assigned := perms.permissionGrantPoliciesAssigned + assigned != null + not compliant +} else := "Unable to determine user consent settings (permissionGrantPoliciesAssigned missing)" if { true } + +# According to Graph, user consent is controlled by authorizationPolicy.defaultUserRolePermissions.permissionGrantPoliciesAssigned. +# CIS intent: user consent should be disabled (empty list). +result := out if { + perms := input.default_user_role_permissions + assigned := perms.permissionGrantPoliciesAssigned + + is_empty := assigned == [] # strict: disabled + ok := is_empty + + out := { + "compliant": compliant_value, + "message": msg, + "details": { + "permission_grant_policies_assigned": assigned, + "permission_grant_policies_assigned_count": assigned_count, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.5.2_admin_consent_workflow_enabled.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.5.2_admin_consent_workflow_enabled.rego new file mode 100644 index 00000000..54f7c38f --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.5.2_admin_consent_workflow_enabled.rego @@ -0,0 +1,50 @@ +# METADATA +# title: Ensure the admin consent workflow is enabled +# description: Enable admin consent workflow for apps. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-admin-consent-workflow +# description: Configure the admin consent workflow +# custom: +# control_id: CIS-5.1.5.2 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Policy.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_1_5_2 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Evaluation failed: unable to retrieve admin consent request policy", + "details": {}, +} + +compliant_value := true if { input.is_enabled == true } else := false if { true } + +msg := "Admin consent workflow is enabled" if { input.is_enabled == true } else := "Admin consent workflow is disabled" if { input.is_enabled == false } else := "Unable to determine admin consent workflow state" if { true } + +result := output if { + enabled := input.is_enabled + reviewers := input.reviewers + + has_reviewers := count(reviewers) > 0 + + output := { + "compliant": compliant_value, + "message": msg, + "details": { + "is_enabled": enabled, + "reviewers_count": count(reviewers), + "has_reviewers": has_reviewers, + "notify_reviewers": input.notify_reviewers, + "reminders_enabled": input.reminders_enabled, + "request_duration_in_days": input.request_duration_in_days, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.6.1_restrict_collaboration_invite_domains.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.6.1_restrict_collaboration_invite_domains.rego new file mode 100644 index 00000000..1c447635 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.6.1_restrict_collaboration_invite_domains.rego @@ -0,0 +1,57 @@ +# METADATA +# title: Ensure that collaboration invitations are sent to allowed domains only +# description: Restrict external collaboration to allowed domains/tenants. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/graph/api/crosstenantaccesspolicy-overview +# description: Cross-tenant access settings +# custom: +# control_id: CIS-5.1.6.1 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Policy.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_1_6_1 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine cross-tenant access restrictions", + "details": {}, +} + +partners := object.get(input, "partners", []) if { true } +partners_count := object.get(input, "partners_count", 0) if { true } + +default_inbound_access_type := access_type if { + inbound := object.get(input, "b2b_collaboration_inbound", {}) + users_and_groups := object.get(inbound, "usersAndGroups", {}) + access_type := object.get(users_and_groups, "accessType", "") +} + +compliant_value := true if { + partners_count > 0 + default_inbound_access_type == "blocked" +} else := false if { true } + +msg := sprintf("Cross-tenant collaboration is restricted by default (partners=%d, accessType=%v)", [partners_count, default_inbound_access_type]) if { compliant_value } else := sprintf("Cross-tenant collaboration is not sufficiently restricted (partners=%d, accessType=%v)", [partners_count, default_inbound_access_type]) if { true } + +# Heuristic evaluation: +# - Consider compliant when the tenant has explicit partner configuration (partners_count > 0) +# and the default inbound B2B collaboration access is not wide-open. +result := output if { + output := { + "compliant": compliant_value, + "message": msg, + "details": { + "partners_count": partners_count, + "partner_tenant_ids": [p.tenantId | some p in partners; p.tenantId != null], + "default_inbound_access_type": default_inbound_access_type, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.6.2_restrict_guest_user_access.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.6.2_restrict_guest_user_access.rego new file mode 100644 index 00000000..78bd9f1f --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.6.2_restrict_guest_user_access.rego @@ -0,0 +1,54 @@ +# METADATA +# title: Ensure that guest user access is restricted +# description: Restrict guest user access permissions in Entra ID. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/entra/identity/users/users-restrict-guest-permissions +# description: Restrict guest access permissions +# custom: +# control_id: CIS-5.1.6.2 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Policy.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_1_6_2 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine guestUserRoleId", + "details": {}, +} + +# Known guestUserRoleId values (Microsoft docs): +# - Same as members: a0b1b346-4d3e-4e8b-98f8-753987be4970 (NOT compliant) +# - Limited access (default): 10dae51f-b6af-4016-8d66-8c2a99b929b3 (compliant) +# - Most restrictive: 2af84b1e-32c8-42b7-82bc-daa82404023b (compliant) + +same_as_member := "a0b1b346-4d3e-4e8b-98f8-753987be4970" if { true } +limited := "10dae51f-b6af-4016-8d66-8c2a99b929b3" if { true } +restricted := "2af84b1e-32c8-42b7-82bc-daa82404023b" if { true } + +ok if { input.guest_user_role_id == limited } +ok if { input.guest_user_role_id == restricted } + +compliant_value := true if { ok } else := false if { true } + +msg := "Guest user access is restricted" if { ok } else := "Guest user access is NOT restricted (guests have member-like permissions)" if { input.guest_user_role_id == same_as_member } else := "Unable to determine guestUserRoleId" if { input.guest_user_role_id == null } else := sprintf("Guest user access is NOT restricted (guestUserRoleId=%v)", [input.guest_user_role_id]) if { true } + +result := out if { + role_id := input.guest_user_role_id + + out := { + "compliant": compliant_value, + "message": msg, + "details": { + "guest_user_role_id": role_id, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.6.3_limit_guest_invitations.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.6.3_limit_guest_invitations.rego new file mode 100644 index 00000000..8531436b --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.1.6.3_limit_guest_invitations.rego @@ -0,0 +1,44 @@ +# METADATA +# title: Ensure guest user invitations are limited to the Guest Inviter role +# description: Limit who can invite guest users into the tenant. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/graph/api/authorizationpolicy-get +# description: authorizationPolicy resource type +# custom: +# control_id: CIS-5.1.6.3 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Policy.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_1_6_3 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine allowInvitesFrom", + "details": {}, +} + +compliant_value := true if { input.allow_invites_from == "adminsAndGuestInviters" } else := false if { true } + +msg := "Guest invitations are limited to admins and Guest Inviter role (allowInvitesFrom=adminsAndGuestInviters)" if { input.allow_invites_from == "adminsAndGuestInviters" } else := sprintf("Guest invitations are not sufficiently restricted (allowInvitesFrom=%v)", [input.allow_invites_from]) if { input.allow_invites_from != null; input.allow_invites_from != "adminsAndGuestInviters" } else := "Unable to determine allowInvitesFrom" if { input.allow_invites_from == null } else := "Unable to determine allowInvitesFrom" if { true } + +result := out if { + v := input.allow_invites_from + + # Expected: only admins and Guest Inviter role can invite + + out := { + "compliant": compliant_value, + "message": msg, + "details": { + "allow_invites_from": v, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.1_mfa_fatigue_protection.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.1_mfa_fatigue_protection.rego new file mode 100644 index 00000000..a27a6bc9 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.1_mfa_fatigue_protection.rego @@ -0,0 +1,49 @@ +# METADATA +# title: Ensure Microsoft Authenticator is configured to protect against MFA fatigue +# description: Configure Authenticator to prevent MFA fatigue attacks. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/entra/identity/authentication/howto-mfa-number-match +# description: Number matching (conceptual) +# custom: +# control_id: CIS-5.2.3.1 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: high +# service: EntraID +# requires_permissions: +# - Policy.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_2_3_1 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine Microsoft Authenticator MFA fatigue protection settings", + "details": {}, +} + +compliant_value := true if { input.number_matching_enabled == true } else := false if { true } + +msg := "MFA fatigue protection is enabled (number matching required)" if { input.number_matching_enabled == true } else := "MFA fatigue protection is not enabled (number matching not required)" if { input.number_matching_enabled != true } else := "Unable to determine Microsoft Authenticator MFA fatigue protection settings" if { true } + +result := output if { + number_matching := input.number_matching_enabled + app_ctx := input.display_app_information_enabled + loc_ctx := input.display_location_information_enabled + + output := { + "compliant": compliant_value, + "message": msg, + "details": { + "state": input.state, + "number_matching_enabled": number_matching, + "display_app_information_enabled": app_ctx, + "display_location_information_enabled": loc_ctx, + "include_targets_count": count(input.include_targets), + "exclude_targets_count": count(input.exclude_targets), + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.2_custom_banned_passwords_enabled.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.2_custom_banned_passwords_enabled.rego new file mode 100644 index 00000000..b425bd3b --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.2_custom_banned_passwords_enabled.rego @@ -0,0 +1,46 @@ +# METADATA +# title: Ensure custom banned passwords lists are used +# description: Configure custom banned password list. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/entra/identity/authentication/concept-password-ban-bad +# description: Password protection and banned passwords (conceptual) +# custom: +# control_id: CIS-5.2.3.2 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Directory.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_2_3_2 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine custom banned password list configuration", + "details": {}, +} + +compliant_value := true if { input.banned_password_list_enabled == true } else := false if { true } + +msg := "Custom banned password list is enabled" if { input.banned_password_list_enabled == true } else := "Custom banned password list is not enabled" if { input.banned_password_list_enabled != true } else := "Unable to determine custom banned password list configuration" if { true } + +banned_list_present := true if { input.banned_password_list != null; input.banned_password_list != "" } else := false if { true } + +result := output if { + enabled := input.banned_password_list_enabled + list := input.banned_password_list + + output := { + "compliant": compliant_value, + "message": msg, + "details": { + "banned_password_list_enabled": enabled, + "banned_password_list_present": banned_list_present, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.3_enable_on_prem_password_protection.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.3_enable_on_prem_password_protection.rego new file mode 100644 index 00000000..daa9afb7 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.3_enable_on_prem_password_protection.rego @@ -0,0 +1,48 @@ +# METADATA +# title: Ensure password protection is enabled for on-prem Active Directory +# description: Enable password protection for on-premises AD. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/entra/identity/authentication/concept-password-ban-bad +# description: Password protection and banned passwords (conceptual) +# custom: +# control_id: CIS-5.2.3.3 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Directory.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_2_3_3 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine on-prem password protection configuration", + "details": {}, +} + +default compliant := false + +compliant if { + input.on_prem_protection_enabled == true +} + +msg := "On-prem password protection is enabled" if { compliant } +msg := "On-prem password protection is not enabled" if { not compliant } + +result := output if { + enabled := input.on_prem_protection_enabled + + output := { + "compliant": compliant, + "message": msg, + "details": { + "on_prem_protection_enabled": enabled, + "enforce_custom_banned_passwords": input.enforce_custom_banned_passwords, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.4_all_members_mfa_capable.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.4_all_members_mfa_capable.rego new file mode 100644 index 00000000..c7b5b6c9 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.4_all_members_mfa_capable.rego @@ -0,0 +1,53 @@ +# METADATA +# title: Ensure all member users are 'MFA capable' +# description: Ensure all users have registered MFA methods. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/graph/api/reportroot-list-authenticationmethods-userregistrationdetails +# description: Authentication methods user registration details report +# custom: +# control_id: CIS-5.2.3.4 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: high +# service: EntraID +# requires_permissions: +# - AuditLog.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_2_3_4 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine MFA capability across users", + "details": {}, +} + +default compliant := false + +compliant if { + input.total_users > 0 + input.mfa_capable_count == input.total_users +} + +msg := "All users are MFA capable" if { compliant } +msg := sprintf("%d of %d users are MFA capable", [input.mfa_capable_count, input.total_users]) if { not compliant } + +result := output if { + total := input.total_users + capable := input.mfa_capable_count + + output := { + "compliant": compliant, + "message": msg, + "details": { + "total_users": total, + "mfa_capable_count": capable, + "mfa_registered_count": input.mfa_registered_count, + "mfa_not_registered_count": input.mfa_not_registered_count, + "mfa_registration_percentage": input.mfa_registration_percentage, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.5_disable_weak_auth_methods.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.5_disable_weak_auth_methods.rego new file mode 100644 index 00000000..5393c8e2 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.5_disable_weak_auth_methods.rego @@ -0,0 +1,50 @@ +# METADATA +# title: Ensure weak authentication methods are disabled +# description: Disable SMS and voice authentication methods. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/graph/api/resources/authenticationmethodspolicy +# description: Authentication methods policy +# custom: +# control_id: CIS-5.2.3.5 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Policy.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_2_3_5 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine SMS/Voice authentication method status", + "details": {}, +} + +default compliant := false + +compliant if { + input.sms_enabled == false + input.voice_enabled == false +} + +msg := "Weak authentication methods (SMS, Voice) are disabled" if { compliant } +msg := sprintf("Weak authentication methods enabled (sms=%v, voice=%v)", [input.sms_enabled, input.voice_enabled]) if { not compliant } + +result := output if { + sms := input.sms_enabled + voice := input.voice_enabled + + output := { + "compliant": compliant, + "message": msg, + "details": { + "sms_enabled": sms, + "voice_enabled": voice, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.6_enable_system_preferred_mfa.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.6_enable_system_preferred_mfa.rego new file mode 100644 index 00000000..105b7c91 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.6_enable_system_preferred_mfa.rego @@ -0,0 +1,56 @@ +# METADATA +# title: Ensure system-preferred multifactor authentication is enabled +# description: Enable system-preferred MFA. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/entra/identity/authentication/howto-mfa-system-preferred +# description: System-preferred MFA (conceptual) +# custom: +# control_id: CIS-5.2.3.6 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Policy.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_2_3_6 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine system-preferred MFA configuration", + "details": {}, +} + +# Note: some tenants may not expose this setting; this remains best-effort. +default compliant := false + +compliant if { + policy := input.authentication_methods_policy + prefs := object.get(policy, "systemCredentialPreferences", {}) + object.get(prefs, "state", null) == "enabled" +} + +msg := "System-preferred MFA is enabled" if { compliant } +msg := sprintf( + "System-preferred MFA is not enabled (state=%v)", + [object.get(object.get(input.authentication_methods_policy, "systemCredentialPreferences", {}), "state", null)], +) if { not compliant } + +# Best-effort: check tenant-level setting on authenticationMethodsPolicy if present. +result := output if { + policy := input.authentication_methods_policy + prefs := object.get(policy, "systemCredentialPreferences", {}) + state := object.get(prefs, "state", null) + + output := { + "compliant": compliant, + "message": msg, + "details": { + "system_credential_preferences_state": state, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.7_disable_email_otp.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.7_disable_email_otp.rego new file mode 100644 index 00000000..7f2b930e --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.2.3.7_disable_email_otp.rego @@ -0,0 +1,46 @@ +# METADATA +# title: Ensure the email OTP authentication method is disabled +# description: Disable email OTP authentication. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/graph/api/resources/authenticationmethodspolicy +# description: Authentication methods policy +# custom: +# control_id: CIS-5.2.3.7 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - Policy.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_2_3_7 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine Email OTP authentication method status", + "details": {}, +} + +default compliant := false + +compliant if { input.email_otp_enabled == false } + +msg := "Email OTP authentication method is disabled" if { compliant } +msg := "Email OTP authentication method is enabled" if { input.email_otp_enabled == true } +msg := "Unable to determine Email OTP authentication method status" if { input.email_otp_enabled == null } + +result := output if { + email := input.email_otp_enabled + + output := { + "compliant": compliant, + "message": msg, + "details": { + "email_otp_enabled": email, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.1_pim_enabled.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.1_pim_enabled.rego new file mode 100644 index 00000000..c541106e --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.1_pim_enabled.rego @@ -0,0 +1,45 @@ +# METADATA +# title: Ensure 'Privileged Identity Management' is used to manage roles +# description: Use PIM for privileged role management. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-configure +# description: Privileged Identity Management (conceptual) +# custom: +# control_id: CIS-5.3.1 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: high +# service: EntraID +# requires_permissions: +# - RoleManagementPolicy.Read.Directory + +package cis.microsoft_365_foundations.v6_0_0.control_5_3_1 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine if PIM is enabled", + "details": {}, +} + +default compliant := false + +compliant if { input.pim_enabled == true } + +msg := "PIM is enabled (role management policies present)" if { compliant } +msg := "PIM does not appear to be enabled (no role management policies found)" if { not compliant } + +result := output if { + enabled := input.pim_enabled + + output := { + "compliant": compliant, + "message": msg, + "details": { + "total_policies": input.total_policies, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.2_guest_access_reviews_configured.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.2_guest_access_reviews_configured.rego new file mode 100644 index 00000000..3cdf3751 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.2_guest_access_reviews_configured.rego @@ -0,0 +1,46 @@ +# METADATA +# title: Ensure 'Access reviews' for Guest Users are configured +# description: Configure access reviews for guest users. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/graph/api/resources/accessreviewset +# description: Access reviews +# custom: +# control_id: CIS-5.3.2 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: medium +# service: EntraID +# requires_permissions: +# - AccessReview.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_3_2 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine guest access reviews", + "details": {}, +} + +default compliant := false + +compliant if { input.has_guest_reviews == true } + +msg := "Guest user access reviews are configured" if { compliant } +msg := "No guest user access reviews are configured" if { not compliant } + +result := output if { + has := input.has_guest_reviews + + output := { + "compliant": compliant, + "message": msg, + "details": { + "total_reviews": input.total_reviews, + "guest_reviews_count": input.guest_reviews_count, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.3_privileged_role_access_reviews_configured.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.3_privileged_role_access_reviews_configured.rego new file mode 100644 index 00000000..ebb0cbd4 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.3_privileged_role_access_reviews_configured.rego @@ -0,0 +1,67 @@ +# METADATA +# title: Ensure 'Access reviews' for privileged roles are configured +# description: Configure access reviews for privileged roles. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/graph/api/resources/accessreviewset +# description: Access reviews +# custom: +# control_id: CIS-5.3.3 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: high +# service: EntraID +# requires_permissions: +# - AccessReview.Read.All + +package cis.microsoft_365_foundations.v6_0_0.control_5_3_3 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine access reviews for privileged roles", + "details": {}, +} + +is_role_query(q) if { contains(q, "role") } +is_role_query(q) if { contains(q, "directoryrole") } + +# Best-effort: compliant if any access review definition query references role-based scope. +default compliant := false + +compliant if { + some d in input.access_review_definitions + scope := d.scope + q := lower(scope.query) + is_role_query(q) +} + +msg := sprintf( + "Found %d access review definition(s) that appear to target roles", + [count([d | some d in input.access_review_definitions; is_role_query(lower(d.scope.query))])], +) if { compliant } + +msg := "No access review definitions appear to target privileged roles" if { not compliant } + +# Best-effort: treat as compliant if any access review definition query references role-based scope. +result := output if { + defs := input.access_review_definitions + role_reviews := [d | + some d in defs + scope := d.scope + q := lower(scope.query) + is_role_query(q) + ] + + output := { + "compliant": compliant, + "message": msg, + "details": { + "total_reviews": input.total_reviews, + "role_reviews_count": count(role_reviews), + "sample_role_review_ids": [d.id | some d in role_reviews], + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.4_ga_activation_requires_approval.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.4_ga_activation_requires_approval.rego new file mode 100644 index 00000000..7802ae86 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.4_ga_activation_requires_approval.rego @@ -0,0 +1,50 @@ +# METADATA +# title: Ensure approval is required for Global Administrator role activation +# description: Require approval for Global Admin role activation. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-role-activation +# description: PIM role activation settings (conceptual) +# custom: +# control_id: CIS-5.3.4 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: critical +# service: EntraID +# requires_permissions: +# - RoleManagementPolicy.Read.Directory + +package cis.microsoft_365_foundations.v6_0_0.control_5_3_4 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine Global Administrator approval requirements", + "details": {}, +} + +default compliant := false + +compliant if { input.global_admin_approval_required == true } + +msg := "Approval is required for Global Administrator activation" if { compliant } +msg := "Approval is NOT required for Global Administrator activation" if { input.global_admin_approval_required == false } +msg := "Unable to determine Global Administrator approval requirements" if { input.global_admin_approval_required == null } + +result := output if { + required := input.global_admin_approval_required + + output := { + "compliant": compliant, + "message": msg, + "details": { + "global_admin_policy": input.global_admin_policy, + "approval_required": required, + "mfa_required": input.global_admin_mfa_required, + "justification_required": input.global_admin_justification_required, + "max_activation_duration": input.global_admin_max_activation_duration, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.5_pra_activation_requires_approval.rego b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.5_pra_activation_requires_approval.rego new file mode 100644 index 00000000..386cdc79 --- /dev/null +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/5.3.5_pra_activation_requires_approval.rego @@ -0,0 +1,47 @@ +# METADATA +# title: Ensure approval is required for Privileged Role Administrator activation +# description: Require approval for Privileged Role Admin activation. +# related_resources: +# - ref: https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-role-activation +# description: PIM role activation settings (conceptual) +# custom: +# control_id: CIS-5.3.5 +# framework: cis +# benchmark: microsoft-365-foundations +# version: v6.0.0 +# severity: critical +# service: EntraID +# requires_permissions: +# - RoleManagementPolicy.Read.Directory + +package cis.microsoft_365_foundations.v6_0_0.control_5_3_5 + +import rego.v1 + +default result := { + "compliant": false, + "message": "Unable to determine Privileged Role Administrator approval requirements", + "details": {}, +} + +default compliant := false + +compliant if { input.privileged_role_admin_approval_required == true } + +msg := "Approval is required for Privileged Role Administrator activation" if { compliant } +msg := "Approval is NOT required for Privileged Role Administrator activation" if { input.privileged_role_admin_approval_required == false } +msg := "Unable to determine Privileged Role Administrator approval requirements" if { input.privileged_role_admin_approval_required == null } + +result := output if { + required := input.privileged_role_admin_approval_required + + output := { + "compliant": compliant, + "message": msg, + "details": { + "privileged_role_admin_policy": input.privileged_role_admin_policy, + "approval_required": required, + }, + } +} + diff --git a/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json b/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json index 92ef0fc9..132bb6a1 100644 --- a/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json +++ b/engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json @@ -76,9 +76,9 @@ "level": "L2", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.groups.groups", - "policy_file": null, + "policy_file": "1.2.1_no_unmanaged_public_groups.rego", "requires_permissions": ["Group.Read.All"], "notes": "Collector exists but control logic not defined" }, @@ -601,9 +601,9 @@ "level": "L2", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.devices.device_management_settings", - "policy_file": null, + "policy_file": "4.1_mark_unmanaged_devices_not_compliant.rego", "requires_permissions": ["DeviceManagementConfiguration.Read.All"], "notes": "Collector exists but control logic not defined" }, @@ -616,9 +616,9 @@ "level": "L2", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.devices.enrollment_restrictions", - "policy_file": null, + "policy_file": "4.2_block_personal_device_enrollment.rego", "requires_permissions": ["DeviceManagementConfiguration.Read.All"], "notes": null }, @@ -646,7 +646,7 @@ "level": "L2", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.policies.authorization_policy", "policy_file": null, "requires_permissions": ["Policy.Read.All"], @@ -661,9 +661,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.policies.authorization_policy", - "policy_file": null, + "policy_file": "5.1.2.3_restrict_tenant_creation.rego", "requires_permissions": ["Policy.Read.All"], "notes": "Check allowedToCreateTenants" }, @@ -721,9 +721,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.groups.groups", - "policy_file": null, + "policy_file": "5.1.3.1_dynamic_guest_group_exists.rego", "requires_permissions": ["Group.Read.All"], "notes": "Collector exists but control logic not defined" }, @@ -736,9 +736,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.policies.authorization_policy", - "policy_file": null, + "policy_file": "5.1.3.2_block_security_group_creation.rego", "requires_permissions": ["Policy.Read.All"], "notes": "Check allowedToCreateSecurityGroups" }, @@ -751,9 +751,9 @@ "level": "L2", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.devices.device_registration_policy", - "policy_file": null, + "policy_file": "5.1.4.1_restrict_device_join.rego", "requires_permissions": ["Policy.Read.All"], "notes": null }, @@ -826,9 +826,9 @@ "level": "L2", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.policies.authorization_policy", - "policy_file": null, + "policy_file": "5.1.4.6_restrict_bitlocker_key_recovery.rego", "requires_permissions": ["Policy.Read.All"], "notes": "Check allowedToReadBitlockerKeysForOwnedDevice" }, @@ -841,9 +841,9 @@ "level": "L2", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.policies.authorization_policy", - "policy_file": null, + "policy_file": "5.1.5.1_block_user_app_consent.rego", "requires_permissions": ["Policy.Read.All"], "notes": null }, @@ -856,9 +856,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.policies.admin_consent_request_policy", - "policy_file": null, + "policy_file": "5.1.5.2_admin_consent_workflow_enabled.rego", "requires_permissions": ["Policy.Read.All"], "notes": null }, @@ -871,9 +871,9 @@ "level": "L2", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.policies.b2b_policy", - "policy_file": null, + "policy_file": "5.1.6.1_restrict_collaboration_invite_domains.rego", "requires_permissions": ["Policy.Read.All"], "notes": null }, @@ -886,9 +886,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.policies.authorization_policy", - "policy_file": null, + "policy_file": "5.1.6.2_restrict_guest_user_access.rego", "requires_permissions": ["Policy.Read.All"], "notes": "Check guestUserRoleId" }, @@ -901,9 +901,9 @@ "level": "L2", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.policies.authorization_policy", - "policy_file": null, + "policy_file": "5.1.6.3_limit_guest_invitations.rego", "requires_permissions": ["Policy.Read.All"], "notes": "Check allowInvitesFrom" }, @@ -1111,9 +1111,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.authentication.mfa_fatigue_protection", - "policy_file": null, + "policy_file": "5.2.3.1_mfa_fatigue_protection.rego", "requires_permissions": ["Policy.Read.All"], "notes": null }, @@ -1126,9 +1126,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.authentication.password_protection", - "policy_file": null, + "policy_file": "5.2.3.2_custom_banned_passwords_enabled.rego", "requires_permissions": ["Policy.Read.All"], "notes": null }, @@ -1141,9 +1141,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.authentication.password_protection", - "policy_file": null, + "policy_file": "5.2.3.3_enable_on_prem_password_protection.rego", "requires_permissions": ["Policy.Read.All"], "notes": null }, @@ -1156,9 +1156,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.authentication.mfa_registration_report", - "policy_file": null, + "policy_file": "5.2.3.4_all_members_mfa_capable.rego", "requires_permissions": ["AuditLog.Read.All"], "notes": null }, @@ -1171,9 +1171,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.authentication.authentication_methods", - "policy_file": null, + "policy_file": "5.2.3.5_disable_weak_auth_methods.rego", "requires_permissions": ["Policy.Read.All"], "notes": "Check SMS/Voice disabled" }, @@ -1186,9 +1186,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.authentication.authentication_methods", - "policy_file": null, + "policy_file": "5.2.3.6_enable_system_preferred_mfa.rego", "requires_permissions": ["Policy.Read.All"], "notes": null }, @@ -1201,9 +1201,9 @@ "level": "L2", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.authentication.authentication_methods", - "policy_file": null, + "policy_file": "5.2.3.7_disable_email_otp.rego", "requires_permissions": ["Policy.Read.All"], "notes": null }, @@ -1231,9 +1231,9 @@ "level": "L2", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.governance.pim_role_policies", - "policy_file": null, + "policy_file": "5.3.1_pim_enabled.rego", "requires_permissions": ["RoleManagement.Read.Directory"], "notes": null }, @@ -1246,9 +1246,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.governance.access_reviews", - "policy_file": null, + "policy_file": "5.3.2_guest_access_reviews_configured.rego", "requires_permissions": ["AccessReview.Read.All"], "notes": null }, @@ -1261,9 +1261,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.governance.access_reviews", - "policy_file": null, + "policy_file": "5.3.3_privileged_role_access_reviews_configured.rego", "requires_permissions": ["AccessReview.Read.All"], "notes": "Combined with pim_role_policies" }, @@ -1276,9 +1276,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.governance.pim_role_policies", - "policy_file": null, + "policy_file": "5.3.4_ga_activation_requires_approval.rego", "requires_permissions": ["RoleManagement.Read.Directory"], "notes": null }, @@ -1291,9 +1291,9 @@ "level": "L1", "is_manual": false, "benchmark_audit_type": "Automated", - "automation_status": "not_started", + "automation_status": "ready", "data_collector_id": "entra.governance.pim_role_policies", - "policy_file": null, + "policy_file": "5.3.5_pra_activation_requires_approval.rego", "requires_permissions": ["RoleManagement.Read.Directory"], "notes": null }, From 7437ea6f92a45daa87b937b6bac9731b0e8de7d2 Mon Sep 17 00:00:00 2001 From: SESH2002 Date: Mon, 19 Jan 2026 00:44:04 +1100 Subject: [PATCH 093/111] Fix Regular Backups ML1/ML2 evaluation and align strategy with evidence rules --- security/evidence/USER_INSTRUCTIONS.md | 226 ------- security/evidence/regular_backups/README.md | 214 +++---- .../regular_backups/USER_INSTRUCTIONS.md | 141 +++++ .../regular_backups/access_admin_only_ml2.txt | 3 +- .../Screenshot 2025-12-09 113241.png | Bin 89006 -> 0 bytes .../Screenshot 2025-12-09 113258.png | Bin 87701 -> 0 bytes .../Screenshot 2025-12-09 113313.png | Bin 71623 -> 0 bytes .../Screenshot 2025-12-09 113355.png | Bin 70178 -> 0 bytes .../Screenshot 2025-12-09 113409.png | Bin 70977 -> 0 bytes .../Screenshot 2025-12-09 113425.png | Bin 72173 -> 0 bytes .../Screenshot 2025-12-09 113442.png | Bin 73204 -> 0 bytes .../Screenshot 2025-12-09 113457.png | Bin 73266 -> 0 bytes .../Screenshot 2025-12-09 113511.png | Bin 72933 -> 0 bytes .../Screenshot 2025-12-09 113536.png | Bin 77192 -> 0 bytes .../Screenshot 2025-12-09 113551.png | Bin 70989 -> 0 bytes .../Screenshot 2025-12-09 113612.png | Bin 70482 -> 0 bytes .../Screenshot 2025-12-09 113629.png | Bin 69610 -> 0 bytes .../Screenshot 2025-12-09 113642.png | Bin 69022 -> 0 bytes .../Screenshot 2025-12-09 113704.png | Bin 74952 -> 0 bytes .../Screenshot 2025-12-09 113721.png | Bin 79979 -> 0 bytes .../Screenshot 2025-12-09 113738.png | Bin 73460 -> 0 bytes .../Screenshot 2025-12-09 114109.png | Bin 72788 -> 0 bytes .../regular_backups/repo_audit_fail.txt | 4 +- .../regular_backups/repo_audit_pass.txt | 3 +- .../restore_report_ml2_fail.txt | 2 - .../restore_report_ml2_pass.txt | 4 +- security/evidence_backend/scanner.py | 4 - security/strategies/__init__.py | 62 +- security/strategies/custom_benchmarks.py | 134 ++-- security/strategies/regular_backups.py | 589 +++++++++++++----- test_regular_backups.py | 48 -- 31 files changed, 784 insertions(+), 650 deletions(-) delete mode 100644 security/evidence/USER_INSTRUCTIONS.md create mode 100644 security/evidence/regular_backups/USER_INSTRUCTIONS.md delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113241.png delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113258.png delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113313.png delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113355.png delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113409.png delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113425.png delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113442.png delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113457.png delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113511.png delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113536.png delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113551.png delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113612.png delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113629.png delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113642.png delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113704.png delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113721.png delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113738.png delete mode 100644 security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 114109.png delete mode 100644 test_regular_backups.py diff --git a/security/evidence/USER_INSTRUCTIONS.md b/security/evidence/USER_INSTRUCTIONS.md deleted file mode 100644 index 293228d9..00000000 --- a/security/evidence/USER_INSTRUCTIONS.md +++ /dev/null @@ -1,226 +0,0 @@ -# Evidence preparation guide – Regular Backups (Essential Eight) - -This guide explains how to prepare evidence files for the Essential Eight – Regular Backups -strategy so the AutoAudit Evidence Scanner can correctly detect -Maturity Level 1 (ML1) and Maturity Level 2 (ML2) results. - -This strategy assesses compliance with the Australian Cyber Security Centre (ACSC) -Essential Eight – Regular Backups control. - -Evidence files must contain clear, consistent key–value style fields such as -status=success, immutability=enabled, or role=backup-admin. - ---- - -## 1. General guidelines for evidence files - -- Files must be plain text (.txt) -- Each file must produce at least one detected result -- Files without `_ml2` are treated as ML1 evidence -- Files with `_ml2` are treated as ML2 evidence -- ML2 evidence always produces both ML1 and ML2 results -- Each file should represent one control outcome -- Evidence may come from: - - backup logs - - audit logs - - policy exports - - restore test reports -- The scanner uses keyword-based detection -- Extra descriptive text is allowed, but required key phrases must be present - ---- - -## 2. Evidence files for ML1 tests - -### 2.1 Successful backups – `backup_success.txt` - -status=success - - ---- - -### 2.2 Failed or missing backups – `backup_failed.txt` - -status=failure -reason=no_recent_backup - - ---- - -### 2.3 Offsite or immutable backups – `offsite_backup.txt` - -backup_location=offsite -immutability=enabled - - ---- - -### 2.4 Successful restore test – `restore_test.txt` - -restore_test=full -status=success - - ---- - -### 2.5 Failed restore test – `restore_failed.txt` - -restore_test=full -status=fail - - ---- - -### 2.6 Backup retention policy – `retention_policy.txt` - -retention_policy=defined -retention_days=30 - - ---- - -### 2.7 Encrypted backups – `Encrypted_backup.txt` - -encryption=aes-256 - - ---- - -### 2.8 Restricted backup access – `access_admin_only.txt` - -access=restricted -role=backup-admin - - ---- - -## 3. Evidence files for ML2 tests - -All ML2 files must include `_ml2` in the filename. - -ML2 evidence always produces: -- ML1 PASS + ML2 PASS, or -- ML1 FAIL + ML2 FAIL - ---- - -### 3.1 Backup verification via audit logs – PASS -`backup_verification_ml2_pass.txt` - -verification=success -status=success - - ---- - -### 3.2 Backup verification via audit logs – FAIL -`backup_verification_ml2_fail.txt` - -verification=fail -audit_log=missing - - ---- - -### 3.3 Offsite & immutability enforced by policy – PASS -`offsite_policy_enforced_ml2.txt` - -backup_location=offsite -immutability=enabled -policy=enforced - - ---- - -### 3.4 Restore to a common point – PASS -`restore_report_ml2_pass.txt` - -restore_test=item-level -status=success -common_point=true - - ---- - -### 3.5 Restore to a common point – FAIL -`restore_report_ml2_fail.txt` - -restore_test=item-level -status=fail - - ---- - -### 3.6 Policy alignment – PASS -`policy_ml2_pass.txt` - -retention_days=30 -immutability=enabled -policy_match=true - - ---- - -### 3.7 Policy misalignment – FAIL -`policy_ml2_fail.txt` - -retention_policy=missing -immutability=disabled - - ---- - -### 3.8 Encryption enforcement – PASS -`encryption_ml2_pass.txt` - -encryption=aes-256 -kms=verified - - ---- - -### 3.9 Encryption enforcement – FAIL -`encryption_ml2_fail.txt` - -encryption=none -kms=missing - - ---- - -### 3.10 Access control enforcement – PASS -`access_admin_only_ml2.txt` - -access=restricted -role=backup-admin -is_backup_admin=true -audit=success - - ---- - -### 3.11 Unauthorized access allowed – FAIL -`repo_audit_fail.txt` - -access=allowed -is_backup_admin=false - - ---- - -## 4. Using the Evidence Scanner - -1. Open the AutoAudit Evidence Scanner -2. Select **Regular Backups (RB)** -3. Upload one or more evidence files -4. Review results: - - Test ID - - Detected maturity level - - PASS or FAIL outcome - -If results are not produced: -- Check filename maturity level -- Verify required key–value fields exist -- Ensure ML2 files include `_ml2` - ---- \ No newline at end of file diff --git a/security/evidence/regular_backups/README.md b/security/evidence/regular_backups/README.md index a48a85ea..bf1cc687 100644 --- a/security/evidence/regular_backups/README.md +++ b/security/evidence/regular_backups/README.md @@ -1,152 +1,126 @@ # Regular Backups (RB) strategy -This strategy checks evidence for Essential Eight Regular Backups controls. +This strategy checks evidence for the ACSC Essential Eight **Regular Backups** control. -It supports detection for both Maturity Level 1 (ML1) and Maturity Level 2 (ML2) requirements -as defined by the Australian Cyber Security Centre (ACSC) Essential Eight framework. +Evidence detection is **key=value driven**, deterministic, and case-insensitive. The parser extracts `key=value` pairs from anywhere in the text. -The strategy analyses plain text evidence such as backup logs, audit records, -policy exports, and restore test reports using keyword-based detection. +> Note: Evidence screenshots must not be committed to the repo. Store them externally (PR attachments, OneDrive, SharePoint) and link them instead. --- ## 1. Tests implemented -| Test ID | Level | Sub-strategy | What it checks - -| ML1-RB-01 | ML1 | Backups are configured and recent | Whether backups are running successfully or failing -| ML1-RB-02 | ML1 | Backups stored offsite or immutable | Whether backups are offsite or immutable -| ML1-RB-03 | ML1 | Restore tests completed | Whether restore tests are performed and their outcome | -| ML1-RB-04 | ML1 | Backup retention policy in place | Whether a retention policy exists | -| ML1-RB-05 | ML1 | Backups encrypted | Whether backups are encrypted -| ML1-RB-06 | ML1 | Backup access restricted | Whether backup access is restricted to administrators -| ML2-RB-01 | ML2 | Backup jobs verified through audit logs | Verification events recorded in audit logs -| ML2-RB-02 | ML2 | Offsite + immutability enforced by policy | Policy enforcement of offsite and immutable backups -| ML2-RB-03 | ML2 | Restore to a common point proven | Ability to restore data consistently to a common point -| ML2-RB-04 | ML2 | Retention and immutability meet policy baseline | Alignment of retention and immutability with policy -| ML2-RB-05 | ML2 | Encryption enforcement verified | Encryption and KMS enforcement -| ML2-RB-06 | ML2 | Access control enforcement for backup administrators | Enforcement of backup administrator access controls +| Test ID | Level | Sub-strategy | What it checks | +|----------|------|--------------|----------------| +| ML1-RB-01 | ML1 | Backups configured and recent | Backup success or failure, including “no recent backup” reasons | +| ML1-RB-02 | ML1 | Offsite OR immutable backups | Backups stored offsite, or immutability enabled | +| ML1-RB-03 | ML1 | Restore test with proven restore point | Successful full restore plus evidence of a common restore point | +| ML1-RB-04 | ML1 | Retention policy | Retention policy defined and retention days present | +| ML1-RB-05 | ML1 | Backup encryption | Encryption enabled, KMS configured where relevant | +| ML1-RB-06 | ML1 | Access control | Backup access restricted to backup administrators | +| ML2-RB-01 | ML2 | Audit verification | Verification recorded and audit log presence | +| ML2-RB-02 | ML2 | Policy enforcement | Policy enforced for offsite and immutability together | +| ML2-RB-03 | ML2 | Restore consistency | Item-level restore success with a common point | +| ML2-RB-04 | ML2 | Policy alignment | Retention and immutability aligned to baseline | +| ML2-RB-05 | ML2 | Encryption enforcement | Encryption enforced with verified KMS | +| ML2-RB-06 | ML2 | Access enforcement | Admin-only access enforced with auditing | --- -## 2. How the scanner decides PASS and FAIL +## 2. How PASS and FAIL are decided -The strategy performs simple keyword checks on the extracted text. +The strategy evaluates extracted `key=value` pairs. Extra descriptive text is allowed. -### ML1 tests +### ML1 rules -#### **ML1-RB-01 Backups are configured and recent** -- PASS if text contains: - `status=success` or `last_backup=` -- FAIL if text contains: - `status=failure` or `reason=no_recent_backup` +**ML1-RB-01 Backups configured and recent** +- PASS when: `status=success` +- FAIL when: `status=failure` or `status=fail` or `reason=no_recent_backup` -#### **ML1-RB-02 Offsite or immutable backups** -- PASS if text contains: - `backup_location=offsite` or `immutability=enabled` or `offsite` +**ML1-RB-02 Offsite OR immutable backups** +- PASS when either: `backup_location=offsite` or `immutability=enabled` +- FAIL when: `backup_location=local` -#### **ML1-RB-03 Restore tests completed** -- PASS if text contains: - `restore_test=full` and `status=success` and `common_point=true` -- FAIL if text contains: - `restore_test=full` and `status=fail` +**ML1-RB-03 Restore test with proven restore point** +- PASS when all are present: `restore_test=full`, `status=success`, and `common_point` (any value) +- FAIL when: `restore_test=full` and (`status=failure` or `status=fail`) -#### **ML1-RB-04 Backup retention policy** -- PASS if text contains: - `retention_policy=defined` or `retention_days=` +**ML1-RB-04 Retention policy** +- PASS when: `retention_policy=defined` and `retention_days` is present +- FAIL when: `retention_policy=missing` or `immutability=disabled`, or when retention is defined but `retention_days` is missing -#### **ML1-RB-05 Backups encrypted** -- PASS if text contains: - `encryption=aes-256` -- FAIL if text contains: - `encryption=none` or `unencrypted backup` or `kms=missing` +**ML1-RB-05 Backup encryption** +- PASS when: `encryption=aes-256` +- FAIL when: `encryption=none` or `kms=missing` (or text contains `unencrypted_backup`) -#### **ML1-RB-06 Backup access restricted** -- PASS if text contains: - `access=restricted` and `role=backup-admin` +**ML1-RB-06 Access control** +- PASS when: `access=restricted` and (`role=backup-admin` or `role=backup_admin` or `is_backup_admin=true`) +- FAIL when: `access=allowed` and the evidence does not indicate a backup admin role ---- +### ML2 rules + +ML2 checks run only when `_ml2` appears in the filename. + +**ML2-RB-01 Audit verification** +- PASS when: `verification=success` +- FAIL when: `verification=fail` or `audit_log=missing` + +**ML2-RB-02 Policy enforcement** +- PASS when all are present: `policy=enforced`, `backup_location=offsite`, `immutability=enabled` +- FAIL when `policy=enforced` is present but offsite and immutability are not both present -### ML2 tests - -#### **ML2-RB-01 Backup jobs verified through audit logs** -- PASS: - Contains `backup job verified` and `verification=success` -- FAIL: - Contains `verification=fail` or `audit log missing` - -#### **ML2-RB-02 Offsite and immutability enforced by policy** -- PASS if text contains: - `immutability=enabled` and `policy=enforced` - -#### **ML2-RB-03 Restore to a common point proven** -- PASS if text contains: - `restore test=item-level` and `status=success` and `common point=` -- FAIL if text contains: - `restore test=item-level` and `status=fail` - -#### **ML2-RB-04 Retention & immutability meet policy baseline** -- PASS: - `retention_days=30` and `immutability=enabled` and `policy_match=true` -- FAIL: - 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�# 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

i%a< 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≦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

  • Q54Kd6lVY(Qo@y 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Ξ@^g2+t=~@?)E6*JE2Fsq*j)^Xh6P$ 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 diff --git a/security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113258.png b/security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113258.png deleted file mode 100644 index d7f3c8e2ca2fdb9524875d92296414fd43641642..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 87701 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 aL{YI{(|mqn#7>O{9+F~mqWRBsz5W-Izit%( diff --git a/security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113355.png b/security/evidence/regular_backups/evidence_screenshots/Screenshot 2025-12-09 113355.png deleted file mode 100644 index cc56e041161e3f62b20c3fa58ffe8a731df73a8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70178 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