From a5a4eae2554cea2a40823108a539cc47c1f07fbe Mon Sep 17 00:00:00 2001 From: bwnyasse <5323628+bwnyasse@users.noreply.github.com> Date: Sat, 10 Jan 2026 11:48:54 -0500 Subject: [PATCH] feat(launchpad): implement secure email hashing for access control This commit introduces a more secure method for verifying user access to the internal launchpad by hashing email addresses. - Replaces the plain-text email list with SHA-256 hashes. - Adds a script to generate these hashes from `iap-users.txt`. - Updates the launchpad HTML to hash the input email and compare it against the `allowed-hashes.json` file. - Updates Makefile to generate hashes before deploy and serve. - Adds .keep file for krow_client folder. --- Makefile | 4 +++ firebase.json | 1 + .../internal-launchpad/allowed-hashes.json | 5 +++ firebase/internal-launchpad/iap-users.txt | 6 ++++ firebase/internal-launchpad/index.html | 23 +++++++------- mobile-apps/apps/krow_client/.keep | 0 scripts/generate-allowed-hashes.js | 31 +++++++++++++++++++ 7 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 firebase/internal-launchpad/allowed-hashes.json create mode 100644 mobile-apps/apps/krow_client/.keep create mode 100644 scripts/generate-allowed-hashes.js diff --git a/Makefile b/Makefile index 2467ccfa..9b4e59e3 100644 --- a/Makefile +++ b/Makefile @@ -124,11 +124,15 @@ build: launchpad-dev: @echo "--> Starting local Launchpad server using Firebase Hosting emulator..." + @echo " - Generating secure email hashes..." + @node scripts/generate-allowed-hashes.js @firebase serve --only hosting:launchpad --project=$(FIREBASE_ALIAS) # --- Deployment --- deploy-launchpad-hosting: @echo "--> Deploying Internal Launchpad to Firebase Hosting..." + @echo " - Generating secure email hashes..." + @node scripts/generate-allowed-hashes.js @echo " - Target: hosting:launchpad" @echo " - Project: $(FIREBASE_ALIAS)" @firebase deploy --only hosting:launchpad --project=$(FIREBASE_ALIAS) diff --git a/firebase.json b/firebase.json index b08c11a0..88dc5410 100644 --- a/firebase.json +++ b/firebase.json @@ -11,6 +11,7 @@ "public": "firebase/internal-launchpad", "ignore": [ "firebase.json", + "iap-users.txt", "**/.*", "**/node_modules/**" ] diff --git a/firebase/internal-launchpad/allowed-hashes.json b/firebase/internal-launchpad/allowed-hashes.json new file mode 100644 index 00000000..a3f1512d --- /dev/null +++ b/firebase/internal-launchpad/allowed-hashes.json @@ -0,0 +1,5 @@ +[ + "1b2e22bdec8f6493bf71ee535b6db6b4b5cd2d373f0ffb25524e229f3b5b7f5f", + "e075ff357ef35be2d55b0e383d59c5256980c492ada7ab84c84b2bb5ac26a73f", + "994b31c1aef3d59fe59bc3b8e1dec860a6fb3c73cbf41bdf45028e2c1ecbcf7a" +] \ No newline at end of file diff --git a/firebase/internal-launchpad/iap-users.txt b/firebase/internal-launchpad/iap-users.txt index c3133c2c..a9e897ef 100644 --- a/firebase/internal-launchpad/iap-users.txt +++ b/firebase/internal-launchpad/iap-users.txt @@ -5,4 +5,10 @@ # Both internal (@krowwithus.com) and external emails are supported. user:admin@krowwithus.com + +# External users - Oloodi employees user:boris@oloodi.com +user:achintha.isuru@oloodi.com + +# External users - Legendary employees + diff --git a/firebase/internal-launchpad/index.html b/firebase/internal-launchpad/index.html index 463cf70a..472f97d7 100644 --- a/firebase/internal-launchpad/index.html +++ b/firebase/internal-launchpad/index.html @@ -398,19 +398,20 @@ async function checkAccess(email) { try { - // Fetch the whitelist - const res = await fetch('iap-users.txt'); + // 1. Fetch the secure list of hashes + const res = await fetch('allowed-hashes.json'); if (!res.ok) return false; - const text = await res.text(); + const allowedHashes = await res.json(); - // Parse lines - const lines = text.split('\n'); - const allowedEmails = lines - .map(line => line.trim()) - .filter(line => line && !line.startsWith('#')) - .map(line => line.replace(/^user:/, '').trim()); - - return allowedEmails.includes(email); + // 2. Hash the user's email (SHA-256) + const normalizedEmail = email.trim().toLowerCase(); + const msgBuffer = new TextEncoder().encode(normalizedEmail); + const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + + // 3. Compare + return allowedHashes.includes(hashHex); } catch (e) { console.error("Failed to check access list:", e); return false; diff --git a/mobile-apps/apps/krow_client/.keep b/mobile-apps/apps/krow_client/.keep new file mode 100644 index 00000000..e69de29b diff --git a/scripts/generate-allowed-hashes.js b/scripts/generate-allowed-hashes.js new file mode 100644 index 00000000..57a09784 --- /dev/null +++ b/scripts/generate-allowed-hashes.js @@ -0,0 +1,31 @@ +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +const INPUT_FILE = path.join(__dirname, '../firebase/internal-launchpad/iap-users.txt'); +const OUTPUT_FILE = path.join(__dirname, '../firebase/internal-launchpad/allowed-hashes.json'); + +try { + const data = fs.readFileSync(INPUT_FILE, 'utf8'); + const lines = data.split('\n'); + + const hashes = lines + .map(line => line.trim()) + .filter(line => line && !line.startsWith('#')) // Ignore empty lines and comments + .map(line => line.replace(/^user:/, '').trim().toLowerCase()) // Clean email + .map(email => { + // Create SHA-256 hash + return crypto.createHash('sha256').update(email).digest('hex'); + }); + + const jsonContent = JSON.stringify(hashes, null, 2); + fs.writeFileSync(OUTPUT_FILE, jsonContent); + + console.log(`✅ Successfully generated ${hashes.length} secure hashes from iap-users.txt`); + console.log(` Output: ${OUTPUT_FILE}`); + +} catch (err) { + console.error('❌ Error generating hashes:', err); + process.exit(1); +} +