bagelhole/devops-security-agent-skills

firebase-app-platform

Build and operate apps on Firebase using Auth, Firestore, Cloud Functions, and Hosting.

소스 보기

사용 방법

  1. 설치 명령을 복사하고 자신의 에이전트 환경에 설치하기 전에 소스 패키지를 확인하세요.
  2. 필요한 도구, 인증 정보, 지원 환경은 원본 문서에서 확인하세요.
  3. 입력과 원하는 산출물을 설명하고 출처와 결과를 대조하세요.
npx skills add https://github.com/bagelhole/devops-security-agent-skills --skill firebase-app-platform

시작 전 확인

  • 설치에는 모델 구독, API 한도, 파일 접근 권한이 포함되지 않습니다.
  • 별점 수와 설치 수는 활동 지표이며 독립적인 품질·보안 검증이 아닙니다.
원본 Skill 문서

원본 저장소의 제목, 예시, 코드, 표, 링크, 이미지를 유지해 표시합니다.

Firebase App Platform

Ship mobile and web backends with Firebase managed services.

When to Use This Skill

Use this skill when:

  • Building mobile or web apps with real-time data sync
  • Need authentication with minimal backend code
  • Prototyping quickly with managed infrastructure
  • Building serverless APIs with Cloud Functions
  • Hosting static sites or SPAs with CDN

Prerequisites

  • Node.js 18+
  • Firebase CLI (npm install -g firebase-tools)
  • Google Cloud account (Firebase is part of GCP)
  • A Firebase project (create at console.firebase.google.com)

Quick Start

bash
# Install and authenticate
npm install -g firebase-tools
firebase login

# Initialize in your project directory
firebase init
# Select: Firestore, Functions, Hosting, Emulators

# Start local emulators
firebase emulators:start

# Deploy everything
firebase deploy

# Deploy specific services
firebase deploy --only functions
firebase deploy --only hosting
firebase deploy --only firestore:rules

Firestore Database

Security Rules

javascript
// firestore.rules
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Users can only read/write their own data
    match /users/{userId} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }

    // Messages: authenticated users can read, only owner can write
    match /channels/{channelId}/messages/{messageId} {
      allow read: if request.auth != null;
      allow create: if request.auth != null
        && request.resource.data.userId == request.auth.uid
        && request.resource.data.body is string
        && request.resource.data.body.size() <= 5000;
      allow update, delete: if request.auth != null
        && resource.data.userId == request.auth.uid;
    }

    // Admin-only collection
    match /admin/{document=**} {
      allow read, write: if request.auth != null
        && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
    }

    // Default: deny everything
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

Data Operations

typescript
// lib/firestore.ts
import { getFirestore, collection, doc, setDoc, getDoc,
         query, where, orderBy, limit, onSnapshot,
         serverTimestamp, increment } from "firebase/firestore";

const db = getFirestore();

// Create document with auto-ID
async function createMessage(channelId: string, body: string, userId: string) {
  const ref = doc(collection(db, "channels", channelId, "messages"));
  await setDoc(ref, {
    body,
    userId,
    createdAt: serverTimestamp(),
  });
  return ref.id;
}

// Real-time listener
function subscribeToMessages(channelId: string, callback: (msgs: any[]) => void) {
  const q = query(
    collection(db, "channels", channelId, "messages"),
    orderBy("createdAt", "desc"),
    limit(50)
  );
  return onSnapshot(q, (snapshot) => {
    const messages = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
    callback(messages);
  });
}

// Atomic counter
async function incrementViews(postId: string) {
  await setDoc(doc(db, "posts", postId), {
    views: increment(1),
  }, { merge: true });
}

Indexes

json
// firestore.indexes.json
{
  "indexes": [
    {
      "collectionGroup": "messages",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "channelId", "order": "ASCENDING" },
        { "fieldPath": "createdAt", "order": "DESCENDING" }
      ]
    }
  ]
}

Authentication

typescript
// lib/auth.ts
import { getAuth, signInWithPopup, GoogleAuthProvider,
         createUserWithEmailAndPassword, signInWithEmailAndPassword,
         signOut, onAuthStateChanged } from "firebase/auth";

const auth = getAuth();

// Google sign-in
async function signInWithGoogle() {
  const provider = new GoogleAuthProvider();
  const result = await signInWithPopup(auth, provider);
  return result.user;
}

// Email/password registration
async function register(email: string, password: string) {
  const result = await createUserWithEmailAndPassword(auth, email, password);
  return result.user;
}

// Auth state listener
onAuthStateChanged(auth, (user) => {
  if (user) {
    console.log("Signed in:", user.uid, user.email);
  } else {
    console.log("Signed out");
  }
});

Cloud Functions

typescript
// functions/src/index.ts
import { onRequest } from "firebase-functions/v2/https";
import { onDocumentCreated } from "firebase-functions/v2/firestore";
import { getFirestore } from "firebase-admin/firestore";
import { initializeApp } from "firebase-admin/app";

initializeApp();
const db = getFirestore();

// HTTP function (API endpoint)
export const api = onRequest({ cors: true, region: "us-central1" }, async (req, res) => {
  if (req.method !== "GET") {
    res.status(405).send("Method not allowed");
    return;
  }
  const snapshot = await db.collection("posts").orderBy("createdAt", "desc").limit(10).get();
  const posts = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
  res.json({ posts });
});

// Firestore trigger — runs when a new message is created
export const onMessageCreated = onDocumentCreated(
  "channels/{channelId}/messages/{messageId}",
  async (event) => {
    const data = event.data?.data();
    if (!data) return;

    // Update channel's last message timestamp
    await db.doc(`channels/${event.params.channelId}`).update({
      lastMessageAt: data.createdAt,
      messageCount: FieldValue.increment(1),
    });

    // Send notification (example)
    console.log(`New message in ${event.params.channelId}: ${data.body.substring(0, 50)}`);
  }
);

Hosting

json
// firebase.json
{
  "hosting": {
    "public": "dist",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      { "source": "/api/**", "function": "api" },
      { "source": "**", "destination": "/index.html" }
    ],
    "headers": [
      {
        "source": "**/*.@(js|css|svg|png|jpg|webp|woff2)",
        "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }]
      },
      {
        "source": "**",
        "headers": [
          { "key": "X-Frame-Options", "value": "DENY" },
          { "key": "X-Content-Type-Options", "value": "nosniff" },
          { "key": "Strict-Transport-Security", "value": "max-age=63072000" }
        ]
      }
    ]
  }
}

Local Emulators

bash
# Start all emulators
firebase emulators:start

# Start specific emulators
firebase emulators:start --only auth,firestore,functions

# Export emulator data for persistence
firebase emulators:export ./emulator-data
firebase emulators:start --import=./emulator-data

# Emulator UI at http://localhost:4000
json
// firebase.json — emulator config
{
  "emulators": {
    "auth": { "port": 9099 },
    "firestore": { "port": 8080 },
    "functions": { "port": 5001 },
    "hosting": { "port": 5000 },
    "ui": { "enabled": true, "port": 4000 }
  }
}

Environment Configuration

bash
# Set environment variables for functions
firebase functions:config:set stripe.key="sk_live_xxx" app.name="MyApp"

# View config
firebase functions:config:get

# Use in functions (v1)
const stripeKey = functions.config().stripe.key;

# For v2 functions, use .env files
# functions/.env
STRIPE_KEY=sk_live_xxx

# functions/.env.local (for emulators)
STRIPE_KEY=sk_test_xxx

Multi-Environment Setup

bash
# Create separate projects for each environment
firebase use --add   # Add staging project alias
firebase use staging # Switch to staging
firebase use production

# Deploy to specific project
firebase deploy --project my-app-staging
firebase deploy --project my-app-production

# .firebaserc
{
  "projects": {
    "staging": "my-app-staging",
    "production": "my-app-production"
  }
}

CLI Reference

bash
firebase projects:list              # List all projects
firebase deploy                      # Deploy everything
firebase deploy --only functions     # Deploy only functions
firebase deploy --only hosting       # Deploy only hosting
firebase deploy --only firestore     # Deploy rules + indexes
firebase functions:log               # View function logs
firebase hosting:channel:create pr-123  # Preview channel
firebase hosting:channel:delete pr-123

Security Best Practices

  • Write strict Firestore security rules before any other code
  • Separate environments by Firebase project (staging/production)
  • Enable budget alerts and quota monitoring in GCP console
  • Move privileged logic into Cloud Functions (never trust the client)
  • Use App Check to prevent API abuse from non-app clients
  • Enable Firestore audit logging for compliance
  • Review OAuth consent screen settings

Troubleshooting

IssueSolution
Permission deniedCheck Firestore rules, verify auth state
Function cold startsUse min instances (minInstances: 1), optimize imports
Emulator won't startCheck port conflicts, run firebase emulators:start --debug
Deploy failsRun firebase deploy --debug, check service account permissions
Rules test failingUse firebase emulators:exec to run rules unit tests

Related Skills

관련 작업

같은 저장소의 Skills

더 많은 Skills

모든 Skills