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.
This commit is contained in:
4
Makefile
4
Makefile
@@ -124,11 +124,15 @@ build:
|
|||||||
|
|
||||||
launchpad-dev:
|
launchpad-dev:
|
||||||
@echo "--> Starting local Launchpad server using Firebase Hosting emulator..."
|
@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)
|
@firebase serve --only hosting:launchpad --project=$(FIREBASE_ALIAS)
|
||||||
|
|
||||||
# --- Deployment ---
|
# --- Deployment ---
|
||||||
deploy-launchpad-hosting:
|
deploy-launchpad-hosting:
|
||||||
@echo "--> Deploying Internal Launchpad to Firebase Hosting..."
|
@echo "--> Deploying Internal Launchpad to Firebase Hosting..."
|
||||||
|
@echo " - Generating secure email hashes..."
|
||||||
|
@node scripts/generate-allowed-hashes.js
|
||||||
@echo " - Target: hosting:launchpad"
|
@echo " - Target: hosting:launchpad"
|
||||||
@echo " - Project: $(FIREBASE_ALIAS)"
|
@echo " - Project: $(FIREBASE_ALIAS)"
|
||||||
@firebase deploy --only hosting:launchpad --project=$(FIREBASE_ALIAS)
|
@firebase deploy --only hosting:launchpad --project=$(FIREBASE_ALIAS)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"public": "firebase/internal-launchpad",
|
"public": "firebase/internal-launchpad",
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"firebase.json",
|
"firebase.json",
|
||||||
|
"iap-users.txt",
|
||||||
"**/.*",
|
"**/.*",
|
||||||
"**/node_modules/**"
|
"**/node_modules/**"
|
||||||
]
|
]
|
||||||
|
|||||||
5
firebase/internal-launchpad/allowed-hashes.json
Normal file
5
firebase/internal-launchpad/allowed-hashes.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
"1b2e22bdec8f6493bf71ee535b6db6b4b5cd2d373f0ffb25524e229f3b5b7f5f",
|
||||||
|
"e075ff357ef35be2d55b0e383d59c5256980c492ada7ab84c84b2bb5ac26a73f",
|
||||||
|
"994b31c1aef3d59fe59bc3b8e1dec860a6fb3c73cbf41bdf45028e2c1ecbcf7a"
|
||||||
|
]
|
||||||
@@ -5,4 +5,10 @@
|
|||||||
# Both internal (@krowwithus.com) and external emails are supported.
|
# Both internal (@krowwithus.com) and external emails are supported.
|
||||||
|
|
||||||
user:admin@krowwithus.com
|
user:admin@krowwithus.com
|
||||||
|
|
||||||
|
# External users - Oloodi employees
|
||||||
user:boris@oloodi.com
|
user:boris@oloodi.com
|
||||||
|
user:achintha.isuru@oloodi.com
|
||||||
|
|
||||||
|
# External users - Legendary employees
|
||||||
|
|
||||||
|
|||||||
@@ -398,19 +398,20 @@
|
|||||||
|
|
||||||
async function checkAccess(email) {
|
async function checkAccess(email) {
|
||||||
try {
|
try {
|
||||||
// Fetch the whitelist
|
// 1. Fetch the secure list of hashes
|
||||||
const res = await fetch('iap-users.txt');
|
const res = await fetch('allowed-hashes.json');
|
||||||
if (!res.ok) return false;
|
if (!res.ok) return false;
|
||||||
const text = await res.text();
|
const allowedHashes = await res.json();
|
||||||
|
|
||||||
// Parse lines
|
// 2. Hash the user's email (SHA-256)
|
||||||
const lines = text.split('\n');
|
const normalizedEmail = email.trim().toLowerCase();
|
||||||
const allowedEmails = lines
|
const msgBuffer = new TextEncoder().encode(normalizedEmail);
|
||||||
.map(line => line.trim())
|
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
||||||
.filter(line => line && !line.startsWith('#'))
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||||
.map(line => line.replace(/^user:/, '').trim());
|
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||||
|
|
||||||
return allowedEmails.includes(email);
|
// 3. Compare
|
||||||
|
return allowedHashes.includes(hashHex);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to check access list:", e);
|
console.error("Failed to check access list:", e);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
0
mobile-apps/apps/krow_client/.keep
Normal file
0
mobile-apps/apps/krow_client/.keep
Normal file
31
scripts/generate-allowed-hashes.js
Normal file
31
scripts/generate-allowed-hashes.js
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user