Files
Krow-workspace/docs/issues/template.md
bwnyasse edf3dc4042 feat: add base44 entity schemas reference
feat: add issue template
feat: add labels.yml with platform:admin label
feat: add setup_admin_console.sh script and update Makefile

This commit introduces several new features and files to the
repository:

- Adds a reference document for Base44 entity schemas to
 ensure data integrity during the migration to the new
 GCP/Firebase backend.
- Adds an issue template to standardize issue reporting and
 ensure all necessary information is included.
- Adds a new label to `labels.yml` for tasks specific to the
 Admin Console web app.
- Adds a shell script to automate the setup of the Admin
 Console web application, including scaffolding,
 dependency installation, and configuration. Also updates
 the Makefile to include new targets for managing the Admin
 Console application.
- Adds issues to create markdown file.
2025-11-14 18:50:24 -05:00

8.9 KiB

[Auth] Implement Firebase Authentication via krowSDK Facade

Labels: feature, infra, platform:web, platform:backend, priority:high, sred-eligible Milestone: Foundation & Dev Environment Setup

🎯 Objective

Replace the Base44 authentication client with a new internal SDK module, krowSDK.auth, backed by Firebase Authentication. This foundational task will unblock all future backend development and align the web application with the new GCP-based architecture.

🔬 SR&ED Justification

  • Technological Uncertainty: What is the optimal way to create a seamless abstraction layer (krowSDK) that perfectly mimics the existing base44 SDK's interface to minimize frontend refactoring, while integrating a completely different authentication provider (Firebase Auth)? A key uncertainty is how this facade will handle the significant differences in user session management and data retrieval (e.g., custom user fields like user_role which are not native to the Firebase Auth user object) between the two systems.
  • Systematic Investigation: We will conduct experimental development to build a krowSDK.auth module. This involves systematically mapping each base44.auth method (me, logout, isAuthenticated) to its Firebase Auth equivalent. We will investigate and prototype a solution for fetching supplementary user data (like user_role) from our Firestore database and merging it with the core Firebase Auth user object. This will establish a clean, reusable, and scalable architectural pattern for all future SDK modules.

Details

This task is the most critical prerequisite for migrating our backend. As defined in the 03-backend-api-specification.md, every request to our new Data Connect and Cloud Functions API will require a Bearer <Firebase-Auth-Token>. Without this, no backend work can proceed.

The Strategy: A Facade SDK (krowSDK)

Instead of replacing base44 calls with Firebase calls directly throughout the codebase, we will create an abstraction layer, or "Facade". This approach has three major benefits:

  1. Simplified Migration: The new krowSDK.auth will expose the exact same methods as the old base44.auth. This means we can swap out the authentication logic with minimal changes to the UI components, drastically reducing the scope of refactoring.
  2. High Maintainability: All authentication logic will be centralized in one place. If we ever need to change providers again, we only modify the SDK, not the entire application.
  3. Clear Separation of Concerns: The UI components remain agnostic about the authentication provider. They just need to call krowSDK.auth.me(), not worry about the underlying implementation details.

Implementation Plan

The developer should create two new files:

1. Firebase Configuration (frontend-web/src/firebase/config.js)

This file will initialize the Firebase app and export the necessary services. It's crucial to use environment variables for the configuration keys to keep them secure and environment-specific.

// frontend-web/src/firebase/config.js

import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";

// Your web app's Firebase configuration
// IMPORTANT: Use environment variables for these values
const firebaseConfig = {
  apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
  authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
  projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
  storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
  appId: import.meta.env.VITE_FIREBASE_APP_ID
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);

// Export Firebase services
export const auth = getAuth(app);
export const db = getFirestore(app);

export default app;

2. Krow SDK (frontend-web/src/lib/krowSDK.js)

This is the core of the task. This file will implement the facade, importing the Firebase auth service and recreating the base44.auth interface.

// frontend-web/src/lib/krowSDK.js

import { auth, db } from '../firebase/config';
import {
  onAuthStateChanged,
  signOut,
  updateProfile,
  // Import other necessary auth functions like signInWithEmailAndPassword, createUserWithEmailAndPassword, etc.
} from "firebase/auth";
import { doc, getDoc } from "firebase/firestore";

/**
 * A promise-based wrapper for onAuthStateChanged to check the current auth state.
 * @returns {Promise<boolean>} - A promise that resolves to true if authenticated, false otherwise.
 */
const isAuthenticated = () => {
  return new Promise((resolve) => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      unsubscribe();
      resolve(!!user);
    });
  });
};

/**
 * Fetches the current authenticated user's profile.
 * This function mimics `base44.auth.me()` by combining the Firebase Auth user
 * with custom data from our Firestore database (e.g., user_role).
 * @returns {Promise<object|null>} - A promise that resolves with the user object or null.
 */
const me = () => {
  return new Promise((resolve, reject) => {
    const unsubscribe = onAuthStateChanged(auth, async (user) => {
      unsubscribe();
      if (user) {
        try {
          // 1. Get the core user from Firebase Auth
          const { uid, email, displayName } = user;
          const baseProfile = {
            id: uid,
            email: email,
            full_name: displayName,
          };

          // 2. Get custom fields from Firestore
          // We assume a 'users' collection where the document ID is the user's UID.
          const userDocRef = doc(db, "users", uid);
          const userDoc = await getDoc(userDocRef);

          if (userDoc.exists()) {
            const customData = userDoc.data();
            // 3. Merge the two data sources
            resolve({
              ...baseProfile,
              user_role: customData.user_role, // Example custom field
              // ... other custom fields from Firestore
            });
          } else {
            // User exists in Auth but not in Firestore. This can happen during sign-up.
            // Resolve with base profile; the app should handle creating the Firestore doc.
            resolve(baseProfile);
          }
        } catch (error) {
          console.error("Error fetching user profile from Firestore:", error);
          reject(error);
        }
      } else {
        // No user is signed in.
        resolve(null);
      }
    });
  });
};

/**
 * Updates the current user's profile.
 * This mimics `base44.auth.updateMe()`.
 * @param {object} profileData - Data to update, e.g., { full_name: "New Name" }.
 */
const updateMe = async (profileData) => {
  if (auth.currentUser) {
    // Firebase Auth's updateProfile only supports displayName and photoURL.
    if (profileData.full_name) {
      await updateProfile(auth.currentUser, {
        displayName: profileData.full_name,
      });
    }
    // For other custom fields, you would need to update the user's document in Firestore.
    // const userDocRef = doc(db, "users", auth.currentUser.uid);
    // await updateDoc(userDocRef, { custom_field: profileData.custom_field });
  } else {
    throw new Error("No authenticated user to update.");
  }
};

/**
 * Logs the user out and redirects them.
 * @param {string} [redirectUrl='/'] - The URL to redirect to after logout.
 */
const logout = (redirectUrl = '/') => {
  signOut(auth).then(() => {
    // Redirect after sign-out.
    window.location.href = redirectUrl;
  }).catch((error) => {
    console.error("Logout failed:", error);
  });
};

// The krowSDK object that mimics the Base44 SDK structure
export const krowSDK = {
  auth: {
    isAuthenticated,
    me,
    updateMe,
    logout,
    // Note: redirectToLogin is not implemented as it's a concern of the routing library (e.g., React Router),
    // which should protect routes and redirect based on the authentication state.
  },
  // Future modules will be added here, e.g., krowSDK.entities.Event
};

Acceptance Criteria

  • A frontend-web/src/firebase/config.js file is created, correctly initializing the Firebase app using environment variables.
  • A frontend-web/src/lib/krowSDK.js file is created and implements the krowSDK.auth facade.
  • The krowSDK.auth module exports isAuthenticated, me, updateMe, and logout functions with interfaces identical to their base44.auth counterparts.
  • Key parts of the application (e.g., login pages, user profile components, auth checks) are refactored to import and use krowSDK.auth instead of base44.auth.
  • A new user can sign up using a Firebase-powered form.
  • An existing user can log in using a Firebase-powered form.
  • The application UI correctly updates to reflect the user's authenticated state (e.g., showing user name, hiding login button).
  • After logging in, the user's Firebase Auth ID Token can be retrieved and is ready to be sent in an Authorization: Bearer header for API calls.