diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..a734374 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# ============================================================================== +# Git Pre-Commit Hook: Anti-Data-Leak & Secrets Protection +# ============================================================================== +# Prevents accidental commits of personal media, child data, student UUIDs, +# local paths, and credentials before code is committed or pushed. +# ============================================================================== + +set -eo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BOLD='\033[1m' +NC='\033[0m' + +ERRORS_FOUND=0 + +echo -e "${BOLD}🔍 Running pre-commit security & data leak checks...${NC}" + +# ------------------------------------------------------------------------------ +# 1. Check for forbidden staged files (Media, Archives, Real Exports) +# ------------------------------------------------------------------------------ +STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM) + +if [ -n "$STAGED_FILES" ]; then + while IFS= read -r file; do + [ -z "$file" ] && continue + + # Check for forbidden export data files & raw media + if [[ "$file" =~ (activities\.csv|activities\.json|exporter_job_info\.json|\.zip$|\.crx$|\.mp4$|\.mov$|\.webm$|\.heic$|\.jpg$|\.jpeg$) ]]; then + echo -e "${RED}❌ [DATA LEAK BLOCKED] Staged forbidden export/media file:${NC} $file" + ERRORS_FOUND=$((ERRORS_FOUND + 1)) + fi + + # Check for forbidden folder names + if [[ "$file" =~ ^(emmy|exports|data|test_data)/ ]]; then + echo -e "${RED}❌ [DATA LEAK BLOCKED] Staged file from sensitive directory:${NC} $file" + ERRORS_FOUND=$((ERRORS_FOUND + 1)) + fi + done <<< "$STAGED_FILES" +fi + +# ------------------------------------------------------------------------------ +# 2. Check diff content for sensitive patterns in staged changes +# (Excludes the pre-commit hook file itself from self-matching) +# ------------------------------------------------------------------------------ +STAGED_DIFF=$(git diff --cached -U0 -- ':!.githooks/*' ':!.git/*' | grep -E "^\+[^+]" || true) + +if [ -n "$STAGED_DIFF" ]; then + # 2a. Check for local machine paths (e.g. /Users/ or /home/) + LOCAL_PATH_MATCH=$(echo "$STAGED_DIFF" | grep -En "(/Users/|/home/)[a-zA-Z0-9_\.-]+" || true) + if [ -n "$LOCAL_PATH_MATCH" ]; then + echo -e "${RED}❌ [DATA LEAK BLOCKED] Detected local machine path (/Users/... or /home/...):${NC}" + echo "$LOCAL_PATH_MATCH" | head -n 3 + ERRORS_FOUND=$((ERRORS_FOUND + 1)) + fi + + # 2b. Check for student UUIDs (e.g. 8-4-4-4-12 hex format) + UUID_MATCH=$(echo "$STAGED_DIFF" | grep -En "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" || true) + if [ -n "$UUID_MATCH" ]; then + echo -e "${RED}❌ [DATA LEAK BLOCKED] Detected hardcoded student/record UUID in diff:${NC}" + echo "$UUID_MATCH" | head -n 3 + ERRORS_FOUND=$((ERRORS_FOUND + 1)) + fi + + # 2c. Check for AWS / Token credentials + AWS_KEY_MATCH=$(echo "$STAGED_DIFF" | grep -En "AKIA[0-9A-Z]{16}" || true) + if [ -n "$AWS_KEY_MATCH" ]; then + echo -e "${RED}❌ [SECURITY LEAK BLOCKED] Detected potential AWS Access Key:${NC}" + echo "$AWS_KEY_MATCH" | head -n 3 + ERRORS_FOUND=$((ERRORS_FOUND + 1)) + fi + + SESSION_TOKEN_MATCH=$(echo "$STAGED_DIFF" | grep -En "(_brightwheel_session=|Bearer[[:space:]]+[A-Za-z0-9_\-\.]{20,})" || true) + if [ -n "$SESSION_TOKEN_MATCH" ]; then + echo -e "${RED}❌ [SECURITY LEAK BLOCKED] Detected session token or bearer credential:${NC}" + echo "$SESSION_TOKEN_MATCH" | head -n 3 + ERRORS_FOUND=$((ERRORS_FOUND + 1)) + fi +fi + +# ------------------------------------------------------------------------------ +# 3. Verdict +# ------------------------------------------------------------------------------ +if [ "$ERRORS_FOUND" -gt 0 ]; then + echo -e "\n${RED}${BOLD}🚨 COMMIT REJECTED:${NC} $ERRORS_FOUND security / data leak issue(s) detected." + echo -e "${YELLOW}Please remove sensitive files/tokens before committing.${NC}" + echo -e "${YELLOW}(If this is an intentional false positive, bypass with 'git commit --no-verify')${NC}\n" + exit 1 +fi + +echo -e "${GREEN}✅ Pre-commit leak check passed! No sensitive data detected.${NC}" +exit 0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ead2724 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,108 @@ +name: CI Verification + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + verify: + name: Lint & Extension Integrity Check + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Validate manifest.json + run: | + echo "🔍 Validating manifest.json syntax and schema..." + node -e " + const manifest = JSON.parse(require('fs').readFileSync('manifest.json', 'utf8')); + if (manifest.manifest_version !== 3) { + console.error('Error: Manifest version must be 3'); + process.exit(1); + } + console.log('✅ manifest.json is valid (v' + manifest.version + ')'); + " + + - name: Verify Referenced Assets & Files + run: | + echo "🔍 Checking that all files referenced in manifest.json exist..." + node -e " + const fs = require('fs'); + const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8')); + const files = [ + manifest.background?.service_worker, + manifest.action?.default_popup, + ...(manifest.content_scripts?.flatMap(cs => cs.js) || []), + ...Object.values(manifest.icons || {}) + ].filter(Boolean); + + let missing = 0; + for (const file of files) { + if (!fs.existsSync(file)) { + console.error('❌ Missing file referenced in manifest:', file); + missing++; + } else { + console.log(' ✓ Found:', file); + } + } + if (missing > 0) process.exit(1); + console.log('✅ All manifest files exist!'); + " + + - name: JavaScript Syntax Check + run: | + echo "🔍 Checking JavaScript syntax..." + find scripts dashboard popup -name "*.js" | while read -r jsfile; do + node --check "$jsfile" + echo " ✓ Syntax valid: $jsfile" + done + echo "✅ All JavaScript files passed syntax verification!" + + - name: Run Anti-Data-Leak Audit + run: | + echo "🔍 Checking repository for sensitive paths and keys..." + ERRORS=0 + + # Check for forbidden export data or test archives + if find . -name "*.zip" -o -name "activities.csv" -o -name "activities.json" | grep -v ".git" | grep -q .; then + echo "❌ Forbidden data/archive file found in repository!" + ERRORS=$((ERRORS + 1)) + fi + + # Check for hardcoded local machine paths + if grep -rEn "(/Users/|/home/)[a-zA-Z0-9_\.-]+" --exclude-dir=".git" --exclude-dir=".githooks" --exclude="*.log" .; then + echo "❌ Hardcoded local path detected in source code!" + ERRORS=$((ERRORS + 1)) + fi + + if [ "$ERRORS" -gt 0 ]; then + echo "🚨 CI Audit failed with $ERRORS issue(s)." + exit 1 + fi + echo "✅ Security & data leak check passed!" + + - name: Test Package Build + run: | + echo "📦 Building test extension package..." + zip -r brightwheel-exporter-test.zip . \ + -x "*.git*" \ + -x ".githooks/*" \ + -x ".github/*" \ + -x "emmy/*" \ + -x "exports/*" \ + -x "data/*" \ + -x "*.md" \ + -x "LICENSE" \ + -x "brightwheel-exporter-test.zip" + + echo "✅ Package built successfully:" + unzip -l brightwheel-exporter-test.zip diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..86cd82d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,71 @@ +name: Build & Package Extension Release + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + build-release: + name: Build ZIP & Attach to GitHub Release + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Read Extension Version + id: version + run: | + VERSION=$(node -p "require('./manifest.json').version") + echo "EXT_VERSION=$VERSION" >> $GITHUB_ENV + echo "Building extension release for version: $VERSION" + + - name: Build Production Extension ZIP + run: | + ZIP_NAME="brightwheel-exporter-v${{ env.EXT_VERSION }}.zip" + echo "📦 Packaging $ZIP_NAME..." + zip -r "$ZIP_NAME" . \ + -x "*.git*" \ + -x ".githooks/*" \ + -x ".github/*" \ + -x "emmy/*" \ + -x "exports/*" \ + -x "data/*" \ + -x "test_data/*" \ + -x "*.md" \ + -x "LICENSE" \ + -x "*.zip" + + echo "ZIP_FILE=$ZIP_NAME" >> $GITHUB_ENV + echo "✅ Package contents verified:" + unzip -l "$ZIP_NAME" + + - name: Upload ZIP to GitHub Release Assets + if: github.event_name == 'release' + uses: softprops/action-gh-release@v2 + with: + files: ${{ env.ZIP_FILE }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # ------------------------------------------------------------------------ + # TODO: Auto-Publish to Chrome Web Store + # ------------------------------------------------------------------------ + # Once you create the initial extension item in the Chrome Web Store Developer + # Dashboard and generate Google Cloud OAuth credentials, uncomment this step + # and configure the following 4 secrets in GitHub (Settings > Secrets > Actions): + # - CHROME_EXTENSION_ID + # - CHROME_CLIENT_ID + # - CHROME_CLIENT_SECRET + # - CHROME_REFRESH_TOKEN + # ------------------------------------------------------------------------ + # - name: Upload & Publish to Chrome Web Store + # uses: fregante/chrome-web-store-upload-action@v2 + # with: + # file-name: ${{ env.ZIP_FILE }} + # extension-id: ${{ secrets.CHROME_EXTENSION_ID }} + # client-id: ${{ secrets.CHROME_CLIENT_ID }} + # client-secret: ${{ secrets.CHROME_CLIENT_SECRET }} + # refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }} + # publish: true diff --git a/.gitignore b/.gitignore index a665f0b..654d95b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,23 @@ -emmy/ \ No newline at end of file +# OS and system files +.DS_Store +Thumbs.db +*.tmp + +# User Export Data & Backups (Prevent leaking child photos/videos/records) +emmy/ +exports/ +data/ +test_data/ +*.zip +*.crx + +# IDE and Editor files +.vscode/ +.idea/ +*.swp +*.swo + +# Node / Package Manager logs (if applicable) +npm-debug.log* +yarn-debug.log* +yarn-error.log* \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0ac652e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 CoderSherlock + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 99436da..2a53938 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,70 @@ -# Brightwheel Memories & Reports Exporter Chrome Extension +# 🌟 Brightwheel Exporter (Chrome Extension) -A Chrome Extension (Manifest V3) that allows parents and guardians to bulk export all of their children's memories, high-resolution photos, videos, daily care reports (meals, naps, potty), learning milestones, and activity manifests from Brightwheel into an organized offline ZIP archive. +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +[![Manifest V3](https://img.shields.io/badge/Manifest-V3-success.svg)](manifest.json) +[![Platform](https://img.shields.io/badge/Platform-Chrome%20Extension-orange.svg)](#installation) + +> **Save and back up all your child's precious preschool & daycare memories, high-resolution photos, HD videos, daily care logs, and developmental milestones from Brightwheel.** + +--- ## ✨ Features -- **Full Data Manifest**: Exports complete student records, activities, and teacher notes in structured `manifest.json` format. -- **High-Resolution Media**: Downloads original full-size photos and videos organized neatly by year and month (`/YYYY/MM/photos/` & `.../videos/`). -- **Daily Care Logs**: Captures meal entries, nap start/end times and durations, diaper/potty logs, and medication logs. -- **Learning Milestones**: Backs up developmental milestones, curriculum tags, and teacher observations. -- **Attendance & Communication**: Includes check-in/out records, health screenings, and message history. -- **Spreadsheet Ready**: Generates an `activities.csv` spreadsheet compatible with Microsoft Excel, Numbers, and Google Sheets. -- **Offline Interactive Timeline Viewer**: Bundles a standalone `index.html` inside the exported ZIP so you can browse your child's photos, search captions, and filter events locally on your computer with no internet required. -- **Safe & Private**: Runs 100% locally in your browser. Seamlessly uses your active Brightwheel logged-in session cookies without asking for passwords or transmitting your family's data to third parties. -- **Smart Rate-Limiting**: Includes automatic exponential backoff (handling HTTP 429 throttling) and configurable pacing delays. +- 📸 **High-Resolution Media Archiver**: Downloads original, full-resolution photos and HD videos organized neatly into date folders (`/photos/YYYY-MM/` and `/videos/YYYY-MM/`). +- 📂 **Direct Local Folder Streaming**: Streams files directly to your chosen local folder via the **File System Access API**—no browser memory crashes or ZIP size limits! +- 📦 **Instant ZIP Archive Export**: One-click bundle creation using zero-compression `STORE` packaging for lightning-fast downloads. +- 🗓️ **Complete Daily Care Records**: Captures meals, exact nap start/end times and calculated durations, diaper & potty logs, check-in/out records, and medication logs. +- 🎯 **Milestones & Learning Observations**: Backs up developmental milestones, curriculum tags, and teacher notes. +- 🌐 **Interactive Offline Timeline Viewer**: Bundles a standalone, elegant `index.html` viewer inside your export so you can search captions, filter events, and relive memories locally with no internet required. +- 📊 **Spreadsheet & Developer Friendly**: Generates a clean `activities.csv` (compatible with Excel, Numbers, and Google Sheets) as well as unified `activities.json` / `manifest.json` files. +- 🛡️ **100% Private & Client-Side**: Operates entirely in your browser using your active logged-in Brightwheel session. No passwords, credentials, or child data are ever transmitted to any third-party servers. +- ⚡ **Smart Throttling & Rate-Limiting**: Built-in exponential backoff automatically handles API pacing and prevents HTTP 429 rate limit errors. --- ## 🚀 Installation (Load Unpacked in Chrome) -1. Open Google Chrome and navigate to `chrome://extensions/`. -2. Turn **ON** the **Developer mode** toggle in the top-right corner. -3. Click the **Load unpacked** button in the top-left corner. -4. Select this directory: +1. **Clone or download this repository:** + ```bash + git clone https://github.com/CoderSherlock/brightwheel-exporter.git ``` - /Users/haopengzhan/Documents/MyGit/brightwheel-exporter-chrome-extension +2. Open Google Chrome and navigate to: ``` -5. The **Brightwheel Memories & Reports Exporter** icon will now appear in your Chrome toolbar! + chrome://extensions/ + ``` +3. Enable **Developer mode** using the toggle switch in the top-right corner. +4. Click the **Load unpacked** button in the top-left corner. +5. Select the cloned `brightwheel-exporter` directory. +6. The **Brightwheel Exporter** icon will now appear in your browser toolbar! --- ## 📖 How to Use 1. Log into your Brightwheel account in Chrome at [https://schools.mybrightwheel.com](https://schools.mybrightwheel.com). -2. Navigate to your child's feed page (e.g. `https://schools.mybrightwheel.com/students/edda433b-2a24-4da7-bbd5-9f131b4b2565/feed`). -3. Click the **Brightwheel Exporter** icon in your browser toolbar (or click the floating **"Export Brightwheel Data"** button on the bottom right of the page). +2. Navigate to your child's page (or open any Brightwheel feed). +3. Click the **Brightwheel Exporter** extension icon in your Chrome toolbar (or click the floating **"Export Brightwheel Data"** button on the bottom right of the page). 4. Click **Open Exporter Dashboard**. 5. In the Dashboard: - - Select your child (or enter a custom student UUID). - - Choose your date range (**All Time**, **This Year**, or **Custom**). - - Select the content categories and export formats you want. + - Select your child from the dropdown (or enter a custom student UUID). + - Choose your date range (**All Time**, **This Year**, or **Custom Date Range**). + - Choose your save mode: + - **Save Directly to Local Folder** *(Recommended for large feeds)* + - **Download as ZIP Archive** + - Select the content categories and export formats you wish to include. - Click **Start Complete Export**. -6. The extension will automatically harvest activities, download media with live progress tracking, and generate your downloadable ZIP archive! +6. Monitor the real-time progress bar and log window as your files and media are safely saved to your computer. --- -## 📁 Exported Archive Structure - -Inside the downloaded `.zip` file, you will find: +## 📁 Exported Folder & Archive Structure ``` [ChildName]_[YYYY-MM-DD]/ -├── index.html # Offline interactive timeline viewer -├── manifest.json # Complete raw structured data -├── activities.csv # Spreadsheet for Excel / Google Sheets +├── index.html # Interactive offline timeline viewer +├── activities.csv # Spreadsheet for Excel / Google Sheets / Numbers +├── activities.json # Complete unified structured data records +├── exporter_job_info.json # Metadata summary & statistics of the export ├── photos/ │ ├── 2026-08/ │ │ ├── 2026-08-20_abc123.jpg @@ -67,5 +79,18 @@ Inside the downloaded `.zip` file, you will find: ## 🔒 Privacy & Security -- **Zero Data Collection**: This extension does not send any personal data, photos, credentials, or cookies to external servers. All requests happen strictly between your browser and Brightwheel/AWS S3. -- **Local Packaging**: The ZIP compression and HTML viewer creation happen entirely in your local browser memory using client-side JSZip. +- **No Remote Servers**: This extension is 100% open source and runs entirely on your local machine. +- **Session Authentication**: Uses your existing Brightwheel browser cookies securely. No logins or API keys are ever stored or shared. +- **Direct S3/CDN Downloads**: Media files are downloaded directly from Brightwheel's official AWS S3 buckets and CloudFront distribution endpoints straight to your disk. + +--- + +## ⚠️ Disclaimer + +This project is an independent, community-driven open-source tool. It is **not affiliated with, endorsed by, sponsored by, or associated with Brightwheel** or its parent companies. "Brightwheel" is a registered trademark of its respective owner. + +--- + +## 📄 License + +Distributed under the [MIT License](LICENSE). See `LICENSE` for more information. diff --git a/manifest.json b/manifest.json index ce9e4b0..9fa5b05 100644 --- a/manifest.json +++ b/manifest.json @@ -38,7 +38,6 @@ "https://*.brightwheel.com/*", "https://*.cloudfront.net/*", "https://*.amazonaws.com/*", - "https://*.s3.amazonaws.com/*", - "" + "https://*.s3.amazonaws.com/*" ] }