mirror of
https://github.com/BagelHole/DevOps-Security-Agent-Skills.git
synced 2026-08-22 12:49:53 +02:00
365 lines
9.6 KiB
Markdown
365 lines
9.6 KiB
Markdown
---
|
|
name: firebase-app-platform
|
|
description: Build and operate apps on Firebase using Auth, Firestore, Cloud Functions, and Hosting. Use when building mobile/web backends with managed services, real-time data sync, or serverless APIs.
|
|
license: MIT
|
|
metadata:
|
|
author: devops-skills
|
|
version: "1.0"
|
|
---
|
|
|
|
# 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
|
|
|
|
| Issue | Solution |
|
|
|-------|---------|
|
|
| Permission denied | Check Firestore rules, verify auth state |
|
|
| Function cold starts | Use min instances (`minInstances: 1`), optimize imports |
|
|
| Emulator won't start | Check port conflicts, run `firebase emulators:start --debug` |
|
|
| Deploy fails | Run `firebase deploy --debug`, check service account permissions |
|
|
| Rules test failing | Use `firebase emulators:exec` to run rules unit tests |
|
|
|
|
## Related Skills
|
|
|
|
- [gcp-cloud-functions](../../cloud-gcp/gcp-cloud-functions/) — Function runtime patterns
|
|
- [vercel-deployments](../vercel-deployments/) — Alternative frontend hosting
|
|
- [convex-backend](../convex-backend/) — Alternative managed backend
|