feat(Makefile): replace Cloud Run deployment with Firebase Hosting for launchpad

feat(firebase.json): remove site property from api-harness hosting config
feat(firebase): migrate launchpad to firebase hosting with auth

This commit migrates the internal launchpad from Cloud Run with IAP
to Firebase Hosting with Firebase Authentication. This change
simplifies the deployment process and leverages Firebase's authentication
capabilities for user access control.

The following changes were made:

- Updated the Makefile to remove Cloud Run deployment tasks and add
 Firebase Hosting deployment tasks.
- Removed the Dockerfile and .gcloudignore files, as they are no longer
 needed for Firebase Hosting.
- Updated the firebase.json file to configure Firebase Hosting for the
 launchpad.
- Modified the launchpad's index.html to include Firebase Authentication
 logic.
- Updated the iap-users.txt file to be used as a whitelist for Firebase
 Authentication.
- Added a launchpad-dev target to run the launchpad locally using the
 Firebase Hosting emulator.
- Removed the configure-iap-launchpad target, as IAP is no longer used.
- Removed the site property from the api-harness hosting configuration
 in firebase.json, as it is not needed.

The migration to Firebase Hosting provides the following benefits:

- Simplified deployment process.
- Firebase Authentication for user access control.
- Improved scalability and reliability.
- Reduced operational overhead.
This commit is contained in:
bwnyasse
2026-01-10 11:18:48 -05:00
parent 4dd9e1949f
commit ba938be7ad
6 changed files with 220 additions and 293 deletions

View File

@@ -21,10 +21,6 @@ GCP_STAGING_PROJECT_ID := krow-workforce-staging
IAP_SERVICE_ACCOUNT := service-933560802882@gcp-sa-iap.iam.gserviceaccount.com IAP_SERVICE_ACCOUNT := service-933560802882@gcp-sa-iap.iam.gserviceaccount.com
# --- Cloud Run Configuration --- # --- Cloud Run Configuration ---
CR_LAUNCHPAD_SERVICE_NAME := internal-launchpad
CR_LAUNCHPAD_REGION := us-central1
CR_LAUNCHPAD_IMAGE_URI := us-docker.pkg.dev/$(GCP_DEV_PROJECT_ID)/gcr-io/$(CR_LAUNCHPAD_SERVICE_NAME)
CR_ADMIN_SERVICE_NAME := admin-console CR_ADMIN_SERVICE_NAME := admin-console
CR_ADMIN_REGION := us-central1 CR_ADMIN_REGION := us-central1
CR_ADMIN_IMAGE_URI = us-docker.pkg.dev/$(GCP_PROJECT_ID)/gcr-io/$(CR_ADMIN_SERVICE_NAME) CR_ADMIN_IMAGE_URI = us-docker.pkg.dev/$(GCP_PROJECT_ID)/gcr-io/$(CR_ADMIN_SERVICE_NAME)
@@ -68,6 +64,7 @@ help:
@echo " make install - Installs web frontend dependencies." @echo " make install - Installs web frontend dependencies."
@echo " make dev - Starts the local web frontend server." @echo " make dev - Starts the local web frontend server."
@echo " make build - Builds the web frontend for production." @echo " make build - Builds the web frontend for production."
@echo " make launchpad-dev - Starts the local launchpad server (Firebase Hosting emulator)."
@echo "" @echo ""
@echo " --- MOBILE APP DEVELOPMENT ---" @echo " --- MOBILE APP DEVELOPMENT ---"
@echo " make mobile-client-install - Install dependencies for client app" @echo " make mobile-client-install - Install dependencies for client app"
@@ -79,7 +76,7 @@ help:
@echo " make mobile-staff-build - Build staff app (requires ENV & PLATFORM, optional BUILD_TYPE=apk)" @echo " make mobile-staff-build - Build staff app (requires ENV & PLATFORM, optional BUILD_TYPE=apk)"
@echo "" @echo ""
@echo " --- DEPLOYMENT ---" @echo " --- DEPLOYMENT ---"
@echo " make deploy-launchpad-full - Deploys internal launchpad to Cloud Run (dev only) with IAP." @echo " make deploy-launchpad-hosting - Deploys internal launchpad to Firebase Hosting (Auth via Firebase)."
@echo " make deploy-admin-full [ENV=staging] - Deploys Admin Console to Cloud Run with IAP (default: dev)." @echo " make deploy-admin-full [ENV=staging] - Deploys Admin Console to Cloud Run with IAP (default: dev)."
@echo " make deploy-app [ENV=staging] - Builds and deploys the main web app via Firebase Hosting (default: dev)." @echo " make deploy-app [ENV=staging] - Builds and deploys the main web app via Firebase Hosting (default: dev)."
@echo "" @echo ""
@@ -125,29 +122,17 @@ build:
@echo "--> Building web frontend for production..." @echo "--> Building web frontend for production..."
@cd frontend-web && VITE_APP_ENV=$(ENV) npm run build @cd frontend-web && VITE_APP_ENV=$(ENV) npm run build
# --- Deployment --- launchpad-dev:
deploy-launchpad: @echo "--> Starting local Launchpad server using Firebase Hosting emulator..."
@echo "--> Building and deploying Internal Launchpad to Cloud Run..." @firebase serve --only hosting:launchpad --project=$(FIREBASE_ALIAS)
@echo " - Step 1: Building container image..."
@cd firebase/internal-launchpad && gcloud builds submit \
--tag $(CR_LAUNCHPAD_IMAGE_URI) \
--project=$(GCP_DEV_PROJECT_ID)
@echo " - Step 2: Deploying to Cloud Run..."
@gcloud run deploy $(CR_LAUNCHPAD_SERVICE_NAME) \
--image $(CR_LAUNCHPAD_IMAGE_URI) \
--platform managed \
--region $(CR_LAUNCHPAD_REGION) \
--no-allow-unauthenticated \
--project=$(GCP_DEV_PROJECT_ID)
@echo " - Step 3: Enabling IAP on the service..."
@gcloud beta run services update $(CR_LAUNCHPAD_SERVICE_NAME) \
--region=$(CR_LAUNCHPAD_REGION) \
--project=$(GCP_DEV_PROJECT_ID) \
--iap
@echo "--> ✅ Deployment to Cloud Run successful."
deploy-launchpad-full: deploy-launchpad configure-iap-launchpad # --- Deployment ---
@echo "✅ Launchpad deployed and IAP configured successfully!" deploy-launchpad-hosting:
@echo "--> Deploying Internal Launchpad to Firebase Hosting..."
@echo " - Target: hosting:launchpad"
@echo " - Project: $(FIREBASE_ALIAS)"
@firebase deploy --only hosting:launchpad --project=$(FIREBASE_ALIAS)
@echo "--> ✅ Deployment to Firebase Hosting successful."
deploy-app: build deploy-app: build
@echo "--> Deploying Frontend Web App to [$(ENV)] environment..." @echo "--> Deploying Frontend Web App to [$(ENV)] environment..."
@@ -214,30 +199,6 @@ free-dev: dataconnect-sync
@cd frontend-web-free && npm run dev -- --port 5174 @cd frontend-web-free && npm run dev -- --port 5174
# --- Cloud IAP Configuration --- # --- Cloud IAP Configuration ---
configure-iap-launchpad:
@echo "--> Configuring IAP for Cloud Run service [$(CR_LAUNCHPAD_SERVICE_NAME)]..."
@echo " - Granting Cloud Run Invoker role to IAP Service Account..."
@gcloud run services add-iam-policy-binding $(CR_LAUNCHPAD_SERVICE_NAME) \
--region=$(CR_LAUNCHPAD_REGION) \
--project=$(GCP_DEV_PROJECT_ID) \
--member=\"serviceAccount:$(IAP_SERVICE_ACCOUNT)\" \
--role='roles/run.invoker' \
--quiet
@echo " - Adding users from iap-users.txt..."
@cd firebase/internal-launchpad && \
grep -v '^#' iap-users.txt | grep -v '^$$' | while read -r member; do \
echo " Adding $$member as IAP-secured Web App User..."; \
gcloud beta iap web add-iam-policy-binding \
--project=$(GCP_DEV_PROJECT_ID) \
--resource-type=cloud-run \
--service=$(CR_LAUNCHPAD_SERVICE_NAME) \
--region=$(CR_LAUNCHPAD_REGION) \
--member=\"$$member\" \
--role='roles/iap.httpsResourceAccessor' \
--quiet; \
done
@echo "✅ IAP configuration for Launchpad complete."
configure-iap-admin: configure-iap-admin:
@echo "--> Configuring IAP for Cloud Run service [$(CR_ADMIN_SERVICE_NAME)] in [$(ENV)]..." @echo "--> Configuring IAP for Cloud Run service [$(CR_ADMIN_SERVICE_NAME)] in [$(ENV)]..."
@echo " - Granting Cloud Run Invoker role to IAP Service Account..." @echo " - Granting Cloud Run Invoker role to IAP Service Account..."

View File

@@ -48,7 +48,6 @@
{ {
"target": "api-harness-dev", "target": "api-harness-dev",
"public": "internal-api-harness/dist", "public": "internal-api-harness/dist",
"site": "krow-api-harness-dev",
"ignore": [ "ignore": [
"firebase.json", "firebase.json",
"**/.*", "**/.*",
@@ -64,7 +63,6 @@
{ {
"target": "api-harness-staging", "target": "api-harness-staging",
"public": "internal-api-harness/dist", "public": "internal-api-harness/dist",
"site": "krow-api-harness-staging",
"ignore": [ "ignore": [
"firebase.json", "firebase.json",
"**/.*", "**/.*",

View File

@@ -1,18 +0,0 @@
# This file specifies files that are *not* uploaded to Google Cloud
# using gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
# $ gcloud topic gcloudignore
#
.gcloudignore
# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore
# Node.js dependencies:
node_modules/
*.log

View File

@@ -1,28 +0,0 @@
# Utiliser nginx pour servir les fichiers statiques
FROM nginx:alpine
# Copier les fichiers statiques
COPY index.html /usr/share/nginx/html/
COPY assets /usr/share/nginx/html/assets/
COPY favicon.svg /usr/share/nginx/html/
COPY logo.svg /usr/share/nginx/html/
# Configuration nginx pour le routing SPA
RUN echo 'server { \
listen 8080; \
server_name _; \
root /usr/share/nginx/html; \
index index.html; \
location / { \
try_files $uri $uri/ /index.html; \
} \
# Headers de sécurité \
add_header X-Frame-Options "SAMEORIGIN" always; \
add_header X-Content-Type-Options "nosniff" always; \
add_header X-XSS-Protection "1; mode=block" always; \
}' > /etc/nginx/conf.d/default.conf
# Nginx écoute sur le port 8080 (requis par Cloud Run)
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,9 +1,8 @@
# List of authorized users for the Internal Launchpad # List of authorized users for the Krow DevOps Launchpad
# Format: one email per line, lines starting with # are comments # Format: one email per line, lines starting with # are comments
# #
# IMPORTANT: These users must belong to the 'krowwithus.com' organization. # Users must be listed here to access the Launchpad via Firebase Auth.
# This is a known limitation of enabling IAP directly on Cloud Run. # Both internal (@krowwithus.com) and external emails are supported.
# See: https://docs.cloud.google.com/run/docs/securing/identity-aware-proxy-cloud-run#known_limitations
user:admin@krowwithus.com user:admin@krowwithus.com
# user:boris@oloodi.com # External users are not supported with this IAP method user:boris@oloodi.com

View File

@@ -4,7 +4,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KROW Launchpad</title> <title>Krow DevOps Launchpad</title>
<link rel="icon" type="image/x-icon" href="favicon.svg"> <link rel="icon" type="image/x-icon" href="favicon.svg">
<!-- Tailwind CSS --> <!-- Tailwind CSS -->
@@ -73,15 +73,8 @@
} }
@keyframes pulse { @keyframes pulse {
0%, 100% { opacity: 1; }
0%, 50% { opacity: .7; }
100% {
opacity: 1;
}
50% {
opacity: .7;
}
} }
.scrollbar-hide::-webkit-scrollbar { .scrollbar-hide::-webkit-scrollbar {
@@ -106,18 +99,14 @@
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
font-size: 0.875rem; font-size: 0.875rem;
color: #4b5563; color: #4b5563;
/* text-gray-600 */
text-align: left; text-align: left;
background-color: #f9fafb; background-color: #f9fafb;
/* bg-gray-50 */
border-radius: 0.5rem; border-radius: 0.5rem;
/* rounded-lg */
transition: background-color 0.2s ease; transition: background-color 0.2s ease;
} }
.accordion-button:hover { .accordion-button:hover {
background-color: #f3f4f6; background-color: #f3f4f6;
/* bg-gray-100 */
} }
.accordion-button .chevron { .accordion-button .chevron {
@@ -134,182 +123,78 @@
max-height: 0; max-height: 0;
} }
/* Modal styles */
.modal-overlay {
backdrop-filter: blur(4px);
animation: fadeIn 0.2s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.modal-content {
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Markdown styling */ /* Markdown styling */
.markdown-content { .markdown-content { line-height: 1.7; color: #374151; }
line-height: 1.7; .markdown-content h1 { font-size: 2em; font-weight: 700; margin-top: 1.5em; margin-bottom: 0.5em; padding-bottom: 0.3em; border-bottom: 2px solid #e5e7eb; color: #111827; }
color: #374151; .markdown-content h1:first-child { margin-top: 0; }
} .markdown-content h2 { font-size: 1.5em; font-weight: 600; margin-top: 1.5em; margin-bottom: 0.5em; padding-bottom: 0.2em; border-bottom: 1px solid #e5e7eb; color: #111827; }
.markdown-content h3 { font-size: 1.25em; font-weight: 600; margin-top: 1.2em; margin-bottom: 0.5em; color: #111827; }
.markdown-content h4 { font-size: 1.1em; font-weight: 600; margin-top: 1em; margin-bottom: 0.5em; color: #111827; }
.markdown-content p { margin-bottom: 1em; }
.markdown-content ul, .markdown-content ol { margin-bottom: 1em; padding-left: 2em; }
.markdown-content ul { list-style-type: disc; }
.markdown-content ol { list-style-type: decimal; }
.markdown-content li { margin-bottom: 0.5em; }
.markdown-content code { background-color: #f3f4f6; padding: 0.2em 0.4em; border-radius: 0.25em; font-size: 0.9em; font-family: 'Courier New', monospace; color: #dc2626; }
.markdown-content pre { background-color: #1f2937; color: #f9fafb; padding: 1em; border-radius: 0.5em; overflow-x: auto; margin-bottom: 1em; }
.markdown-content pre code { background-color: transparent; padding: 0; color: inherit; }
.markdown-content blockquote { border-left: 4px solid #3b82f6; padding-left: 1em; margin: 1em 0; color: #6b7280; font-style: italic; }
.markdown-content a { color: #3b82f6; text-decoration: underline; }
.markdown-content a:hover { color: #2563eb; }
.markdown-content table { width: 100%; border-collapse: collapse; margin-bottom: 1em; }
.markdown-content th, .markdown-content td { border: 1px solid #e5e7eb; padding: 0.5em; text-align: left; }
.markdown-content th { background-color: #f9fafb; font-weight: 600; }
.markdown-content img { max-width: 100%; height: auto; border-radius: 0.5em; margin: 1em 0; }
.markdown-content hr { border: none; border-top: 2px solid #e5e7eb; margin: 2em 0; }
.markdown-content h1 { /* Loading Overlay */
font-size: 2em; #auth-loading {
font-weight: 700; position: fixed; top: 0; left: 0; right: 0; bottom: 0;
margin-top: 1.5em; background: white; z-index: 50;
margin-bottom: 0.5em; display: flex; flex-direction: column; align-items: center; justify-content: center;
padding-bottom: 0.3em;
border-bottom: 2px solid #e5e7eb;
color: #111827;
} }
#login-screen {
.markdown-content h1:first-child { display: none;
margin-top: 0; position: fixed; top: 0; left: 0; right: 0; bottom: 0;
} background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
z-index: 40;
.markdown-content h2 { align-items: center; justify-content: center;
font-size: 1.5em;
font-weight: 600;
margin-top: 1.5em;
margin-bottom: 0.5em;
padding-bottom: 0.2em;
border-bottom: 1px solid #e5e7eb;
color: #111827;
}
.markdown-content h3 {
font-size: 1.25em;
font-weight: 600;
margin-top: 1.2em;
margin-bottom: 0.5em;
color: #111827;
}
.markdown-content h4 {
font-size: 1.1em;
font-weight: 600;
margin-top: 1em;
margin-bottom: 0.5em;
color: #111827;
}
.markdown-content p {
margin-bottom: 1em;
}
.markdown-content ul,
.markdown-content ol {
margin-bottom: 1em;
padding-left: 2em;
}
.markdown-content ul {
list-style-type: disc;
}
.markdown-content ol {
list-style-type: decimal;
}
.markdown-content li {
margin-bottom: 0.5em;
}
.markdown-content code {
background-color: #f3f4f6;
padding: 0.2em 0.4em;
border-radius: 0.25em;
font-size: 0.9em;
font-family: 'Courier New', monospace;
color: #dc2626;
}
.markdown-content pre {
background-color: #1f2937;
color: #f9fafb;
padding: 1em;
border-radius: 0.5em;
overflow-x: auto;
margin-bottom: 1em;
}
.markdown-content pre code {
background-color: transparent;
padding: 0;
color: inherit;
}
.markdown-content blockquote {
border-left: 4px solid #3b82f6;
padding-left: 1em;
margin: 1em 0;
color: #6b7280;
font-style: italic;
}
.markdown-content a {
color: #3b82f6;
text-decoration: underline;
}
.markdown-content a:hover {
color: #2563eb;
}
.markdown-content table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1em;
}
.markdown-content th,
.markdown-content td {
border: 1px solid #e5e7eb;
padding: 0.5em;
text-align: left;
}
.markdown-content th {
background-color: #f9fafb;
font-weight: 600;
}
.markdown-content img {
max-width: 100%;
height: auto;
border-radius: 0.5em;
margin: 1em 0;
}
.markdown-content hr {
border: none;
border-top: 2px solid #e5e7eb;
margin: 2em 0;
} }
</style> </style>
</head> </head>
<body class="bg-gradient-to-br from-gray-50 to-gray-100 min-h-screen"> <body class="bg-gradient-to-br from-gray-50 to-gray-100 min-h-screen">
<div class="flex h-screen overflow-hidden">
<!-- Auth Loading State -->
<div id="auth-loading">
<div class="w-16 h-16 border-4 border-primary-200 border-t-primary-600 rounded-full animate-spin mb-4"></div>
<p class="text-gray-500 font-medium">Verifying access...</p>
</div>
<!-- Login Screen -->
<div id="login-screen" class="flex">
<div class="bg-white p-8 rounded-2xl shadow-xl max-w-md w-full mx-4 text-center">
<div class="flex justify-center mb-6">
<img src="logo.svg" alt="KROW Logo" class="h-16 w-16" onerror="this.src='https://via.placeholder.com/64?text=KROW'">
</div>
<h1 class="text-2xl font-bold text-gray-900 mb-2">Internal DevOps Launchpad</h1>
<p class="text-gray-500 mb-8">Please sign in to access internal development resources.</p>
<button id="google-signin-btn" class="w-full flex items-center justify-center space-x-3 bg-white border border-gray-300 hover:bg-gray-50 text-gray-700 font-medium py-3 px-4 rounded-xl transition-all shadow-sm">
<svg class="w-5 h-5" viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" />
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" />
</svg>
<span>Sign in with Google</span>
</button>
<p id="login-error" class="mt-4 text-red-500 text-sm hidden"></p>
</div>
</div>
<!-- Main Content (Protected) -->
<div id="app-content" class="hidden flex h-screen overflow-hidden">
<!-- Sidebar --> <!-- Sidebar -->
<aside class="w-72 bg-white shadow-xl flex flex-col border-r border-gray-200"> <aside class="w-72 bg-white shadow-xl flex flex-col border-r border-gray-200">
@@ -320,8 +205,8 @@
<img src="logo.svg" alt="Krow Logo" style="height: 60px;" onerror="this.style.display='none'"> <img src="logo.svg" alt="Krow Logo" style="height: 60px;" onerror="this.style.display='none'">
</div> </div>
<div> <div>
<h1 class="text-xl font-bold text-gray-900">KROW</h1> <h1 class="text-xl font-bold text-gray-900">KROW DevOps</h1>
<p class="text-xs text-gray-500">Launchpad</p> <p class="text-xs text-gray-500">Launchpad Hub</p>
</div> </div>
</div> </div>
</div> </div>
@@ -347,10 +232,16 @@
<!-- Footer --> <!-- Footer -->
<div class="p-4 border-t border-gray-200 bg-gray-50"> <div class="p-4 border-t border-gray-200 bg-gray-50">
<div class="flex flex-col space-y-2">
<div class="flex items-center space-x-2 text-xs text-gray-500"> <div class="flex items-center space-x-2 text-xs text-gray-500">
<div class="w-2 h-2 bg-green-500 rounded-full badge-pulse"></div> <div class="w-2 h-2 bg-green-500 rounded-full badge-pulse"></div>
<span>System Online</span> <span>System Online</span>
</div> </div>
<div class="flex items-center justify-between pt-2 border-t border-gray-100">
<span id="user-email" class="text-xs text-gray-400 truncate max-w-[150px]"></span>
<button id="logout-btn" class="text-xs text-red-500 hover:text-red-700 font-medium">Sign Out</button>
</div>
</div>
</div> </div>
</aside> </aside>
@@ -361,8 +252,8 @@
<div id="home-view" class="p-8"> <div id="home-view" class="p-8">
<!-- Header --> <!-- Header -->
<div class="mb-8"> <div class="mb-8">
<h2 class="text-3xl font-bold text-gray-900 mb-2">Welcome to KROW Launchpad</h2> <h2 class="text-3xl font-bold text-gray-900 mb-2">Krow DevOps Launchpad</h2>
<p class="text-gray-600">Your central hub for workforce management infrastructure</p> <p class="text-gray-600">Central hub for KROW development and operations infrastructure</p>
</div> </div>
<div id="links-container" class="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div id="links-container" class="grid grid-cols-1 lg:grid-cols-2 gap-6">
@@ -428,6 +319,130 @@
<!-- Panzoom --> <!-- Panzoom -->
<script src="https://cdn.jsdelivr.net/npm/@panzoom/panzoom@4.5.1/dist/panzoom.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@panzoom/panzoom@4.5.1/dist/panzoom.min.js"></script>
<!-- Firebase Auth Logic -->
<script type="module">
import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.8.0/firebase-app.js';
import { getAuth, GoogleAuthProvider, signInWithPopup, onAuthStateChanged, signOut } from 'https://www.gstatic.com/firebasejs/10.8.0/firebase-auth.js';
// Elements
const authLoading = document.getElementById('auth-loading');
const loginScreen = document.getElementById('login-screen');
const appContent = document.getElementById('app-content');
const googleBtn = document.getElementById('google-signin-btn');
const logoutBtn = document.getElementById('logout-btn');
const loginError = document.getElementById('login-error');
const userEmailSpan = document.getElementById('user-email');
let pendingError = null;
// Initialize Firebase
// Note: fetch('/__/firebase/init.json') works only on deployed Firebase Hosting or firebase serve
async function initAuth() {
try {
let config;
// Fetch config from hosting environment
const res = await fetch('/__/firebase/init.json');
if (!res.ok) throw new Error('Failed to load Firebase config. Ensure you are running via "make launchpad-dev" or deployed on Hosting.');
config = await res.json();
console.log("Firebase Config Loaded:", config.projectId);
const app = initializeApp(config);
const auth = getAuth(app);
// Auth State Observer
onAuthStateChanged(auth, async (user) => {
if (user) {
// User is signed in, check if allowed
const allowed = await checkAccess(user.email);
if (allowed) {
showApp(user);
} else {
pendingError = `Access Denied: <b>${user.email}</b> is not authorized.<br>Please contact the Krow Internal Developers or an Administrator to request access.`;
signOut(auth);
}
} else {
// User is signed out
if (pendingError) {
showLogin(pendingError);
pendingError = null;
} else {
showLogin();
}
}
});
// Login Handler
googleBtn.addEventListener('click', () => {
// Clear previous errors
loginError.classList.add('hidden');
const provider = new GoogleAuthProvider();
signInWithPopup(auth, provider).catch((error) => {
console.error("Login failed:", error);
showLogin("Login failed: " + error.message);
});
});
// Logout Handler
logoutBtn.addEventListener('click', () => {
signOut(auth);
});
} catch (e) {
console.error("Auth init error:", e);
// Fallback for local dev without firebase serving
if (window.location.hostname === 'localhost') {
alert("Firebase Auth failed to load. Ensure you are using 'firebase serve' or have configured local auth.");
}
}
}
async function checkAccess(email) {
try {
// Fetch the whitelist
const res = await fetch('iap-users.txt');
if (!res.ok) return false;
const text = await res.text();
// 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);
} catch (e) {
console.error("Failed to check access list:", e);
return false;
}
}
function showApp(user) {
authLoading.style.display = 'none';
loginScreen.style.display = 'none';
appContent.classList.remove('hidden');
userEmailSpan.textContent = user.email;
// Trigger link loading if not already done
if (typeof loadLinks === 'function') loadLinks();
}
function showLogin(errorMsg) {
authLoading.style.display = 'none';
appContent.classList.add('hidden');
loginScreen.style.display = 'flex';
if (errorMsg) {
loginError.innerHTML = errorMsg;
loginError.classList.remove('hidden');
} else {
loginError.classList.add('hidden');
}
}
// Start
initAuth();
</script>
<script> <script>
let allDiagrams = []; let allDiagrams = [];
let allDocuments = []; let allDocuments = [];