Add comprehensive Terraform infrastructure with Firebase automation
- Create Firebase project, web app, and Firestore database - Automate Firebase Authentication with email templates - Configure security rules for user data isolation - Support Cloud Run and App Engine hosting options - Add professional email templates for password reset and verification - Include deployment scripts and comprehensive documentation - Implement service accounts with minimal required permissions - Add Docker configuration for containerized deployment 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
28
terraform/Dockerfile
Normal file
28
terraform/Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Use Python 3.11 slim image
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy requirements first for better layer caching
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Create non-root user (security best practice)
|
||||||
|
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD curl -f http://localhost:5000/ || exit 1
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "2", "--timeout", "120", "app:app"]
|
||||||
262
terraform/FIREBASE_AUTH.md
Normal file
262
terraform/FIREBASE_AUTH.md
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
# Firebase Authentication Automation with Terraform
|
||||||
|
|
||||||
|
This guide explains how Firebase Authentication settings are automated in the Rothbard Law Group deployment.
|
||||||
|
|
||||||
|
## 🚀 What's Automated
|
||||||
|
|
||||||
|
### 1. Authentication Providers
|
||||||
|
- **Email/Password**: Enabled by default
|
||||||
|
- **Google Sign-In**: Optional (controlled by `enable_google_signin` variable)
|
||||||
|
- **Phone, Facebook, Apple**: Disabled for security
|
||||||
|
|
||||||
|
### 2. Email Templates
|
||||||
|
- **Password Reset**: Professional HTML and text templates
|
||||||
|
- **Email Verification**: Welcome templates with branding
|
||||||
|
- **Customizable**: From address, name, and reply-to settings
|
||||||
|
|
||||||
|
### 3. Security Rules
|
||||||
|
- **Firestore Rules**: Users can only access their own data
|
||||||
|
- **Authentication Required**: All database operations require auth
|
||||||
|
- **Profile Access**: Users can read/write their own profile only
|
||||||
|
|
||||||
|
### 4. Firebase Hosting
|
||||||
|
- **Static Asset Hosting**: Optional for CSS, JS, images
|
||||||
|
- **Caching Headers**: Optimized performance
|
||||||
|
- **URL Rewrites**: Proper routing for SPA
|
||||||
|
|
||||||
|
## 📋 Configuration Variables
|
||||||
|
|
||||||
|
Add these to your `terraform.tfvars`:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# Authentication Settings
|
||||||
|
enable_google_signin = false # Set to true to enable Google Sign-In
|
||||||
|
|
||||||
|
# Email Configuration
|
||||||
|
auth_from_email = "noreply@rothbardlaw.com"
|
||||||
|
auth_from_name = "Rothbard Law Group"
|
||||||
|
auth_reply_to = "support@rothbardlaw.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Authentication Provider Setup
|
||||||
|
|
||||||
|
### Email/Password (Default)
|
||||||
|
```hcl
|
||||||
|
sign_in_options {
|
||||||
|
email {
|
||||||
|
enabled = true
|
||||||
|
password_required = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Google Sign-In (Optional)
|
||||||
|
To enable Google Sign-In:
|
||||||
|
|
||||||
|
1. **Set variable**:
|
||||||
|
```hcl
|
||||||
|
enable_google_signin = true
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Configure OAuth in Google Cloud**:
|
||||||
|
```bash
|
||||||
|
# Enable Google+ API
|
||||||
|
gcloud services enable plus.googleapis.com
|
||||||
|
|
||||||
|
# Create OAuth consent screen
|
||||||
|
gcloud alpha iap oauth-clients create \
|
||||||
|
--display-name="Rothbard Portal" \
|
||||||
|
--brand="Rothbard Law Group"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Update Firebase Console**:
|
||||||
|
- Go to Firebase Console → Authentication → Sign-in method
|
||||||
|
- Enable Google provider
|
||||||
|
- Add your OAuth client ID and secret
|
||||||
|
|
||||||
|
## 📧 Email Template Customization
|
||||||
|
|
||||||
|
### Template Files
|
||||||
|
- `templates/reset_password.html` - Password reset HTML
|
||||||
|
- `templates/reset_password.txt` - Password reset text
|
||||||
|
- `templates/email_verification.html` - Email verification HTML
|
||||||
|
- `templates/email_verification.txt` - Email verification text
|
||||||
|
|
||||||
|
### Customization Options
|
||||||
|
- **Branding**: Update colors, logos in HTML templates
|
||||||
|
- **Contact Info**: Change address, phone numbers
|
||||||
|
- **Content**: Modify welcome messages and instructions
|
||||||
|
|
||||||
|
### Email Variables Available
|
||||||
|
- `{{ resetLink }}` - Password reset URL
|
||||||
|
- `{{ verificationLink }}` - Email verification URL
|
||||||
|
- `{{ userEmail }}` - User's email address
|
||||||
|
|
||||||
|
## 🔒 Security Rules Explained
|
||||||
|
|
||||||
|
### Firestore Rules
|
||||||
|
```javascript
|
||||||
|
// Users can only access their own profile
|
||||||
|
match /users/{userId} {
|
||||||
|
allow read, write: if request.auth.uid == userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All other collections require authentication
|
||||||
|
match /{collection=**} {
|
||||||
|
allow read, write: if request.auth != null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Security Features
|
||||||
|
- **User Isolation**: Users can't access other users' data
|
||||||
|
- **Authentication Required**: No anonymous access
|
||||||
|
- **Self-Service**: Users can only modify their own profiles
|
||||||
|
|
||||||
|
## 🛠️ Advanced Configuration
|
||||||
|
|
||||||
|
### Multi-Factor Authentication
|
||||||
|
Currently disabled for simplicity. To enable:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
multi_factor_auth {
|
||||||
|
enabled = true
|
||||||
|
provider_configs {
|
||||||
|
phone {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Email Templates
|
||||||
|
For more advanced templates, you can use Firebase Admin SDK:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# In your Flask app
|
||||||
|
from firebase_admin import auth
|
||||||
|
|
||||||
|
def send_custom_email(user_email, template_name):
|
||||||
|
# Custom email sending logic
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Domain Restrictions
|
||||||
|
To restrict authentication to specific domains:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// In Firebase Auth security rules
|
||||||
|
rules_version = '2';
|
||||||
|
service cloud.firestore {
|
||||||
|
match /users/{userId} {
|
||||||
|
allow read, write: if
|
||||||
|
request.auth != null &&
|
||||||
|
request.auth.token.email.matches('.*@rothbardlaw\\.com$');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Monitoring and Analytics
|
||||||
|
|
||||||
|
### Authentication Events
|
||||||
|
Track these events in your application:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Log authentication events
|
||||||
|
def log_auth_event(event_type, user_id, details=None):
|
||||||
|
db.collection('auth_logs').add({
|
||||||
|
'event_type': event_type,
|
||||||
|
'user_id': user_id,
|
||||||
|
'timestamp': firestore.SERVER_TIMESTAMP,
|
||||||
|
'details': details or {}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Events to Monitor
|
||||||
|
- User registrations
|
||||||
|
- Password resets
|
||||||
|
- Failed login attempts
|
||||||
|
- Email verifications
|
||||||
|
|
||||||
|
## 🔄 Updates and Maintenance
|
||||||
|
|
||||||
|
### Updating Email Templates
|
||||||
|
1. Edit template files in `terraform/templates/`
|
||||||
|
2. Run `terraform apply` to update
|
||||||
|
3. Changes apply to new emails immediately
|
||||||
|
|
||||||
|
### Adding New Providers
|
||||||
|
1. Update `google_identitytoolkit_config` in `main.tf`
|
||||||
|
2. Add provider-specific variables
|
||||||
|
3. Configure OAuth credentials in Google Cloud
|
||||||
|
4. Apply Terraform changes
|
||||||
|
|
||||||
|
### Security Rule Updates
|
||||||
|
1. Modify `google_firestore_security_policy` in `main.tf`
|
||||||
|
2. Test rules in Firebase Console first
|
||||||
|
3. Apply with Terraform
|
||||||
|
|
||||||
|
## 🚨 Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
1. **Email Templates Not Working**
|
||||||
|
- Check template file paths
|
||||||
|
- Verify template syntax
|
||||||
|
- Check email provider settings
|
||||||
|
|
||||||
|
2. **Authentication Provider Not Working**
|
||||||
|
- Verify API credentials
|
||||||
|
- Check provider configuration
|
||||||
|
- Review Firebase Console settings
|
||||||
|
|
||||||
|
3. **Security Rules Blocking Access**
|
||||||
|
- Test rules in Firebase Console
|
||||||
|
- Check user authentication status
|
||||||
|
- Verify collection/document paths
|
||||||
|
|
||||||
|
### Debug Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check Firebase Auth configuration
|
||||||
|
gcloud auth troubleshoot
|
||||||
|
|
||||||
|
# Test authentication flow
|
||||||
|
curl -X POST "https://identitytoolkit.googleapis.com/v1/accounts:signIn?key=YOUR_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"email":"user@example.com","password":"password","returnSecureToken":true}'
|
||||||
|
|
||||||
|
# Check Firestore rules
|
||||||
|
gcloud firestore databases rules describe \
|
||||||
|
--project=your-project-id
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Additional Resources
|
||||||
|
|
||||||
|
- [Firebase Authentication Documentation](https://firebase.google.com/docs/auth)
|
||||||
|
- [Terraform Google Provider](https://registry.terraform.io/providers/hashicorp/google/latest/docs)
|
||||||
|
- [Firestore Security Rules](https://firebase.google.com/docs/firestore/security/get-started)
|
||||||
|
- [Firebase Email Templates](https://firebase.google.com/docs/auth/custom-email-templates)
|
||||||
|
|
||||||
|
## 🎯 Best Practices
|
||||||
|
|
||||||
|
1. **Security First**
|
||||||
|
- Use HTTPS everywhere
|
||||||
|
- Implement proper session management
|
||||||
|
- Regular security audits
|
||||||
|
|
||||||
|
2. **User Experience**
|
||||||
|
- Clear error messages
|
||||||
|
- Professional email templates
|
||||||
|
- Mobile-responsive design
|
||||||
|
|
||||||
|
3. **Maintenance**
|
||||||
|
- Regular backups
|
||||||
|
- Monitoring and alerts
|
||||||
|
- Documentation updates
|
||||||
|
|
||||||
|
4. **Compliance**
|
||||||
|
- GDPR compliance for EU users
|
||||||
|
- Data retention policies
|
||||||
|
- Privacy policy alignment
|
||||||
|
|
||||||
|
This automation ensures your Firebase Authentication is secure, professional, and maintainable while following industry best practices for legal client portals.
|
||||||
297
terraform/README.md
Normal file
297
terraform/README.md
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
# Rothbard Law Group - Terraform Deployment
|
||||||
|
|
||||||
|
This Terraform configuration deploys the Rothbard Law Group client portal to Google Cloud Platform with Firebase backend.
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||||
|
│ Firebase App │ │ Firestore │ │ Cloud Run │
|
||||||
|
│ (Client Auth) │◄──►│ (User Data) │◄──►│ (Flask App) │
|
||||||
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ Filevine API │
|
||||||
|
│ (Case Data) │
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hosting Options
|
||||||
|
|
||||||
|
### 1. Cloud Run (Recommended)
|
||||||
|
- **Best for production use**
|
||||||
|
- Container-based deployment
|
||||||
|
- Scales from 0 to N instances
|
||||||
|
- Cost-effective (pay-per-use)
|
||||||
|
- Full control over environment
|
||||||
|
|
||||||
|
### 2. App Engine
|
||||||
|
- Platform-as-a-service
|
||||||
|
- Built-in scaling
|
||||||
|
- Slightly more restrictive
|
||||||
|
- Good for simpler deployments
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. **Google Cloud SDK**
|
||||||
|
```bash
|
||||||
|
curl https://sdk.cloud.google.com | bash
|
||||||
|
gcloud init
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Terraform**
|
||||||
|
```bash
|
||||||
|
# macOS
|
||||||
|
brew install terraform
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
sudo apt-get install terraform
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Docker** (for Cloud Run)
|
||||||
|
```bash
|
||||||
|
# macOS
|
||||||
|
brew install docker
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
sudo apt-get install docker.io
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup Instructions
|
||||||
|
|
||||||
|
### 1. Configure Terraform Variables
|
||||||
|
|
||||||
|
Copy the example variables file:
|
||||||
|
```bash
|
||||||
|
cp terraform.tfvars.example terraform.tfvars
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit `terraform.tfvars` with your values:
|
||||||
|
```hcl
|
||||||
|
gcp_project_id = "your-gcp-project-id"
|
||||||
|
domain_name = "rothbard.yourdomain.com"
|
||||||
|
hosting_option = "cloud_run" # or "app_engine"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Set Environment Variables
|
||||||
|
|
||||||
|
Create a `.env` file for sensitive data:
|
||||||
|
```bash
|
||||||
|
# Flask Configuration
|
||||||
|
FLASK_SECRET_KEY=your-super-secret-key
|
||||||
|
|
||||||
|
# Firebase Configuration
|
||||||
|
FIREBASE_API_KEY=your-firebase-api-key
|
||||||
|
FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
|
||||||
|
FIREBASE_PROJECT_ID=your-gcp-project-id
|
||||||
|
FIREBASE_APP_ID=your-firebase-app-id
|
||||||
|
|
||||||
|
# Service Account (optional - can use gcloud auth)
|
||||||
|
FIREBASE_SERVICE_ACCOUNT_JSON='{"type":"service_account",...}'
|
||||||
|
|
||||||
|
# Filevine API Configuration
|
||||||
|
FILEVINE_CLIENT_ID=your-filevine-client-id
|
||||||
|
FILEVINE_CLIENT_SECRET=your-filevine-client-secret
|
||||||
|
FILEVINE_PERSONAL_ACCESS_TOKEN=your-filevine-pat
|
||||||
|
FILEVINE_ORG_ID=your-filevine-org-id
|
||||||
|
FILEVINE_USER_ID=your-filevine-user-id
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Deploy with Cloud Run (Recommended)
|
||||||
|
|
||||||
|
#### Step 1: Build and Push Docker Image
|
||||||
|
```bash
|
||||||
|
# Set your project ID
|
||||||
|
export PROJECT_ID=your-gcp-project-id
|
||||||
|
|
||||||
|
# Build the Docker image
|
||||||
|
docker build -t gcr.io/$PROJECT_ID/rothbard-portal:latest .
|
||||||
|
|
||||||
|
# Push to Google Container Registry
|
||||||
|
docker push gcr.io/$PROJECT_ID/rothbard-portal:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 2: Deploy Infrastructure
|
||||||
|
```bash
|
||||||
|
cd terraform
|
||||||
|
|
||||||
|
# Initialize Terraform
|
||||||
|
terraform init
|
||||||
|
|
||||||
|
# Plan the deployment
|
||||||
|
terraform plan
|
||||||
|
|
||||||
|
# Apply the changes
|
||||||
|
terraform apply
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 3: Update Container Image Variable
|
||||||
|
After the first deployment, update your `terraform.tfvars`:
|
||||||
|
```hcl
|
||||||
|
container_image = "gcr.io/your-gcp-project-id/rothbard-portal:latest"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run `terraform apply` again.
|
||||||
|
|
||||||
|
### 4. Deploy with App Engine
|
||||||
|
|
||||||
|
#### Step 1: Create Source Package
|
||||||
|
```bash
|
||||||
|
# Copy app files and create zip
|
||||||
|
cp app.py requirements.txt templates/ app-engine/
|
||||||
|
cd app-engine
|
||||||
|
zip -r ../app-source.zip .
|
||||||
|
cd ..
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 2: Deploy Infrastructure
|
||||||
|
```bash
|
||||||
|
cd terraform
|
||||||
|
|
||||||
|
# Set hosting option to app_engine
|
||||||
|
# Edit terraform.tfvars: hosting_option = "app_engine"
|
||||||
|
|
||||||
|
# Initialize and apply
|
||||||
|
terraform init
|
||||||
|
terraform plan
|
||||||
|
terraform apply
|
||||||
|
```
|
||||||
|
|
||||||
|
## Post-Deployment Setup
|
||||||
|
|
||||||
|
### 1. Firebase Configuration
|
||||||
|
|
||||||
|
After deployment, you'll need to:
|
||||||
|
|
||||||
|
1. **Configure Firebase Authentication**:
|
||||||
|
- Go to Firebase Console → Authentication
|
||||||
|
- Enable Email/Password provider
|
||||||
|
- Add your domain to authorized domains
|
||||||
|
|
||||||
|
2. **Set up Firestore Rules**:
|
||||||
|
```javascript
|
||||||
|
rules_version = '2';
|
||||||
|
service cloud.firestore {
|
||||||
|
match /databases/{database}/documents {
|
||||||
|
match /users/{userId} {
|
||||||
|
allow read, write: if request.auth != null && request.auth.uid == userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Enable Users
|
||||||
|
|
||||||
|
Users need to be manually enabled in Firestore:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// In Firebase Console → Firestore Database
|
||||||
|
// Create document in collection 'users' with document ID = Firebase UID
|
||||||
|
{
|
||||||
|
enabled: true,
|
||||||
|
caseEmail: "user@example.com", // This filters Filevine projects
|
||||||
|
name: "Client Name"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Custom Domain (Optional)
|
||||||
|
|
||||||
|
For Cloud Run with custom domain:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Map your domain to the Cloud Run service
|
||||||
|
gcloud run services add-iam-policy-binding rothbard-portal-service \
|
||||||
|
--member="allUsers" \
|
||||||
|
--role="roles/run.invoker"
|
||||||
|
|
||||||
|
# Set up domain mapping
|
||||||
|
gcloud run domain-mappings create \
|
||||||
|
--domain=rothbard.yourdomain.com \
|
||||||
|
--service=rothbard-portal-service
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitoring and Maintenance
|
||||||
|
|
||||||
|
### View Logs
|
||||||
|
```bash
|
||||||
|
# Cloud Run logs
|
||||||
|
gcloud logs read "resource.type=cloud_run_revision" --limit 50
|
||||||
|
|
||||||
|
# App Engine logs
|
||||||
|
gcloud app logs tail -s default
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scale Configuration
|
||||||
|
|
||||||
|
For Cloud Run, you can adjust scaling in `terraform/modules/cloud_run/main.tf`:
|
||||||
|
```hcl
|
||||||
|
metadata {
|
||||||
|
annotations = {
|
||||||
|
"autoscaling.knative.dev/maxScale" = "10" # Max instances
|
||||||
|
"autoscaling.knative.dev/minScale" = "1" # Min instances
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Updates
|
||||||
|
|
||||||
|
To update the application:
|
||||||
|
|
||||||
|
1. **Cloud Run**: Build and push new Docker image, then update the `container_image` variable
|
||||||
|
2. **App Engine**: Update source code, create new zip, and redeploy
|
||||||
|
|
||||||
|
## Costs
|
||||||
|
|
||||||
|
### Cloud Run (Recommended)
|
||||||
|
- **Free tier**: 180,000 vCPU-seconds, 360,000 GiB-seconds memory per month
|
||||||
|
- **Beyond free tier**: ~$0.000024 per vCPU-second, $0.0000025 per GiB-second
|
||||||
|
- **Typical cost**: $5-20/month for low traffic client portal
|
||||||
|
|
||||||
|
### App Engine
|
||||||
|
- **Free tier**: 28 instance-hours per day
|
||||||
|
- **Beyond free tier**: ~$0.05 per instance-hour
|
||||||
|
- **Typical cost**: $10-50/month depending on traffic
|
||||||
|
|
||||||
|
### Firebase
|
||||||
|
- **Spark plan**: Free for most use cases
|
||||||
|
- **Blaze plan**: Pay-as-you-go for high usage
|
||||||
|
- **Typical cost**: $0-25/month depending on authentication usage
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
1. **Service Account**: Minimal permissions granted (Firestore and Firebase Admin)
|
||||||
|
2. **Secret Management**: Sensitive data stored in Secret Manager
|
||||||
|
3. **HTTPS**: All traffic encrypted with TLS
|
||||||
|
4. **Authentication**: Firebase ID tokens verified server-side
|
||||||
|
5. **Access Control**: User access controlled through Firestore profiles
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
1. **Build failures**: Check Dockerfile and requirements.txt
|
||||||
|
2. **Permission errors**: Verify service account has correct IAM roles
|
||||||
|
3. **Firebase connection**: Ensure service account key is properly formatted
|
||||||
|
4. **CORS errors**: Configure Firebase Auth domain correctly
|
||||||
|
|
||||||
|
### Debug Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check Cloud Run service status
|
||||||
|
gcloud run services describe rothbard-portal-service
|
||||||
|
|
||||||
|
# Test service locally
|
||||||
|
docker run -p 5000:5000 gcr.io/$PROJECT_ID/rothbard-portal:latest
|
||||||
|
|
||||||
|
# Check Terraform state
|
||||||
|
terraform show
|
||||||
|
```
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues with:
|
||||||
|
- **Terraform deployment**: Check Terraform logs and state
|
||||||
|
- **Cloud Run**: View Cloud Run logs in Google Console
|
||||||
|
- **Firebase**: Check Firebase Console for configuration issues
|
||||||
|
- **Application**: Review Flask application logs
|
||||||
29
terraform/app.yaml
Normal file
29
terraform/app.yaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# App Engine configuration file
|
||||||
|
runtime: python311
|
||||||
|
|
||||||
|
instance_class: F1
|
||||||
|
automatic_scaling:
|
||||||
|
min_idle_instances: automatic
|
||||||
|
max_idle_instances: 1
|
||||||
|
min_pending_latency: automatic
|
||||||
|
max_pending_latency: automatic
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
env_variables:
|
||||||
|
FLASK_ENV: production
|
||||||
|
PORT: 8080
|
||||||
|
|
||||||
|
# Handlers
|
||||||
|
handlers:
|
||||||
|
- url: /.*
|
||||||
|
script: auto
|
||||||
|
secure: always
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
health_check:
|
||||||
|
enable_health_check: true
|
||||||
|
check_path: "/"
|
||||||
|
|
||||||
|
# Runtime configuration
|
||||||
|
runtime_config:
|
||||||
|
python_version: 3
|
||||||
229
terraform/deploy.sh
Executable file
229
terraform/deploy.sh
Executable file
@@ -0,0 +1,229 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Rothbard Law Group Deployment Script
|
||||||
|
# This script automates the deployment process for Cloud Run
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
PROJECT_ID=""
|
||||||
|
DOMAIN_NAME=""
|
||||||
|
HOSTING_OPTION="cloud_run"
|
||||||
|
|
||||||
|
# Helper functions
|
||||||
|
log_info() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check prerequisites
|
||||||
|
check_prerequisites() {
|
||||||
|
log_info "Checking prerequisites..."
|
||||||
|
|
||||||
|
# Check gcloud
|
||||||
|
if ! command -v gcloud &> /dev/null; then
|
||||||
|
log_error "gcloud is not installed. Please install Google Cloud SDK."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check terraform
|
||||||
|
if ! command -v terraform &> /dev/null; then
|
||||||
|
log_error "terraform is not installed. Please install Terraform."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check docker (for Cloud Run)
|
||||||
|
if [ "$HOSTING_OPTION" = "cloud_run" ] && ! command -v docker &> /dev/null; then
|
||||||
|
log_error "docker is not installed. Please install Docker for Cloud Run deployment."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Prerequisites check passed!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Setup project
|
||||||
|
setup_project() {
|
||||||
|
log_info "Setting up Google Cloud project..."
|
||||||
|
|
||||||
|
if [ -z "$PROJECT_ID" ]; then
|
||||||
|
read -p "Enter your GCP Project ID: " PROJECT_ID
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set the project
|
||||||
|
gcloud config set project "$PROJECT_ID"
|
||||||
|
|
||||||
|
# Enable required APIs
|
||||||
|
log_info "Enabling required APIs..."
|
||||||
|
gcloud services enable run.googleapis.com
|
||||||
|
gcloud services enable cloudbuild.googleapis.com
|
||||||
|
gcloud services enable firestore.googleapis.com
|
||||||
|
gcloud services enable firebase.googleapis.com
|
||||||
|
gcloud services enable secretmanager.googleapis.com
|
||||||
|
|
||||||
|
log_info "Project setup completed!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build and push Docker image (Cloud Run)
|
||||||
|
build_and_push_image() {
|
||||||
|
if [ "$HOSTING_OPTION" != "cloud_run" ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Building Docker image..."
|
||||||
|
|
||||||
|
# Build the image
|
||||||
|
docker build -t gcr.io/$PROJECT_ID/rothbard-portal:latest .
|
||||||
|
|
||||||
|
log_info "Pushing Docker image to Google Container Registry..."
|
||||||
|
|
||||||
|
# Configure Docker to use gcloud as a credential helper
|
||||||
|
gcloud auth configure-docker
|
||||||
|
|
||||||
|
# Push the image
|
||||||
|
docker push gcr.io/$PROJECT_ID/rothbard-portal:latest
|
||||||
|
|
||||||
|
log_info "Docker image pushed successfully!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deploy infrastructure with Terraform
|
||||||
|
deploy_infrastructure() {
|
||||||
|
log_info "Deploying infrastructure with Terraform..."
|
||||||
|
|
||||||
|
cd terraform
|
||||||
|
|
||||||
|
# Create terraform.tfvars if it doesn't exist
|
||||||
|
if [ ! -f "terraform.tfvars" ]; then
|
||||||
|
log_warn "terraform.tfvars not found. Creating from example..."
|
||||||
|
cp terraform.tfvars.example terraform.tfvars
|
||||||
|
|
||||||
|
# Update with project ID
|
||||||
|
sed -i "s/your-gcp-project-id/$PROJECT_ID/g" terraform.tfvars
|
||||||
|
|
||||||
|
if [ -n "$DOMAIN_NAME" ]; then
|
||||||
|
sed -i "s/rothbard-portal.example.com/$DOMAIN_NAME/g" terraform.tfvars
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_warn "Please edit terraform/terraform.tfvars with your specific configuration before continuing."
|
||||||
|
read -p "Press Enter to continue after editing..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Initialize Terraform
|
||||||
|
terraform init
|
||||||
|
|
||||||
|
# Plan deployment
|
||||||
|
log_info "Planning Terraform deployment..."
|
||||||
|
terraform plan
|
||||||
|
|
||||||
|
# Apply deployment
|
||||||
|
log_info "Applying Terraform configuration..."
|
||||||
|
terraform apply -auto-approve
|
||||||
|
|
||||||
|
# Get the output URL
|
||||||
|
APP_URL=$(terraform output -raw application_url)
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
log_info "Infrastructure deployed successfully!"
|
||||||
|
log_info "Application URL: $APP_URL"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create service account key for local development
|
||||||
|
create_service_account_key() {
|
||||||
|
log_info "Creating service account key for development..."
|
||||||
|
|
||||||
|
SA_NAME="rothbard-flask-app"
|
||||||
|
SA_EMAIL="$SA_NAME@$PROJECT_ID.iam.gserviceaccount.com"
|
||||||
|
|
||||||
|
# Create service account if it doesn't exist
|
||||||
|
if ! gcloud iam service-accounts describe "$SA_EMAIL" &> /dev/null; then
|
||||||
|
gcloud iam service-accounts create "$SA_NAME" \
|
||||||
|
--display-name="Rothbard Flask App Service Account"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Grant necessary roles
|
||||||
|
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
|
||||||
|
--member="serviceAccount:$SA_EMAIL" \
|
||||||
|
--role="roles/datastore.user"
|
||||||
|
|
||||||
|
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
|
||||||
|
--member="serviceAccount:$SA_EMAIL" \
|
||||||
|
--role="roles/firebase.admin"
|
||||||
|
|
||||||
|
# Create key
|
||||||
|
gcloud iam service-accounts keys create ~/rothbard-service-account.json \
|
||||||
|
--iam-account="$SA_EMAIL"
|
||||||
|
|
||||||
|
log_info "Service account key created at ~/rothbard-service-account.json"
|
||||||
|
log_warn "Add this to your environment: export GOOGLE_APPLICATION_CREDENTIALS=~/rothbard-service-account.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main deployment function
|
||||||
|
main() {
|
||||||
|
log_info "Starting Rothbard Law Group deployment..."
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-p|--project)
|
||||||
|
PROJECT_ID="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-d|--domain)
|
||||||
|
DOMAIN_NAME="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-o|--option)
|
||||||
|
HOSTING_OPTION="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
echo "Usage: $0 [OPTIONS]"
|
||||||
|
echo "Options:"
|
||||||
|
echo " -p, --project PROJECT_ID GCP Project ID"
|
||||||
|
echo " -d, --domain DOMAIN_NAME Domain name (optional)"
|
||||||
|
echo " -o, --option HOSTING_OPTION Hosting option (cloud_run or app_engine)"
|
||||||
|
echo " -h, --help Show this help message"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "Unknown option: $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Validate hosting option
|
||||||
|
if [[ ! "$HOSTING_OPTION" =~ ^(cloud_run|app_engine)$ ]]; then
|
||||||
|
log_error "Invalid hosting option. Use 'cloud_run' or 'app_engine'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_prerequisites
|
||||||
|
setup_project
|
||||||
|
build_and_push_image
|
||||||
|
deploy_infrastructure
|
||||||
|
create_service_account_key
|
||||||
|
|
||||||
|
log_info "Deployment completed successfully!"
|
||||||
|
log_info "Next steps:"
|
||||||
|
log_info "1. Configure Firebase Authentication in the Firebase Console"
|
||||||
|
log_info "2. Set up Firestore security rules"
|
||||||
|
log_info "3. Enable user accounts in Firestore"
|
||||||
|
log_info "4. Configure your custom domain (if applicable)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main function
|
||||||
|
main "$@"
|
||||||
99
terraform/deployment.tf
Normal file
99
terraform/deployment.tf
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
variable "hosting_option" {
|
||||||
|
description = "Hosting option for the Flask app"
|
||||||
|
type = string
|
||||||
|
default = "cloud_run"
|
||||||
|
validation {
|
||||||
|
condition = contains(["cloud_run", "app_engine"], var.hosting_option)
|
||||||
|
error_message = "The hosting_option must be one of: cloud_run, app_engine."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Select hosting option based on variable
|
||||||
|
module "hosting" {
|
||||||
|
source = "./modules/${var.hosting_option}"
|
||||||
|
|
||||||
|
# Common variables
|
||||||
|
app_name = "rothbard-portal"
|
||||||
|
gcp_project_id = var.gcp_project_id
|
||||||
|
gcp_region = var.gcp_region
|
||||||
|
firebase_project_id = google_firebase_project.default.project
|
||||||
|
flask_secret_key = var.flask_secret_key
|
||||||
|
service_account_email = google_service_account.flask_app.email
|
||||||
|
service_account_key_data = var.service_account_key_data
|
||||||
|
|
||||||
|
# Filevine credentials
|
||||||
|
filevine_client_id = var.filevine_client_id
|
||||||
|
filevine_client_secret = var.filevine_client_secret
|
||||||
|
filevine_pat = var.filevine_pat
|
||||||
|
filevine_org_id = var.filevine_org_id
|
||||||
|
filevine_user_id = var.filevine_user_id
|
||||||
|
|
||||||
|
# Module-specific variables
|
||||||
|
container_image = var.hosting_option == "cloud_run" ? var.container_image : null
|
||||||
|
app_source_zip_path = var.hosting_option == "app_engine" ? var.app_source_zip_path : null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Additional variables for hosting options
|
||||||
|
variable "flask_secret_key" {
|
||||||
|
description = "Flask secret key"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "service_account_key_data" {
|
||||||
|
description = "Service account key JSON data"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "container_image" {
|
||||||
|
description = "Docker image for Cloud Run deployment"
|
||||||
|
type = string
|
||||||
|
default = "gcr.io/your-project/rothbard-portal:latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "app_source_zip_path" {
|
||||||
|
description = "Path to App Engine source zip"
|
||||||
|
type = string
|
||||||
|
default = "./app-source.zip"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Filevine credentials
|
||||||
|
variable "filevine_client_id" {
|
||||||
|
description = "Filevine client ID"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "filevine_client_secret" {
|
||||||
|
description = "Filevine client secret"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "filevine_pat" {
|
||||||
|
description = "Filevine personal access token"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "filevine_org_id" {
|
||||||
|
description = "Filevine organization ID"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "filevine_user_id" {
|
||||||
|
description = "Filevine user ID"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Output hosting-specific URLs
|
||||||
|
output "application_url" {
|
||||||
|
description = "URL of the deployed application"
|
||||||
|
value = var.hosting_option == "cloud_run" ? module.hosting.service_url :
|
||||||
|
var.hosting_option == "app_engine" ? module.hosting.app_url :
|
||||||
|
null
|
||||||
|
}
|
||||||
241
terraform/main.tf
Normal file
241
terraform/main.tf
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
google = {
|
||||||
|
source = "hashicorp/google"
|
||||||
|
version = "~> 5.0"
|
||||||
|
}
|
||||||
|
firebase = {
|
||||||
|
source = "terraform-google-modules/firebase/google"
|
||||||
|
version = "~> 13.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required_version = ">= 1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "google" {
|
||||||
|
project = var.gcp_project_id
|
||||||
|
region = var.gcp_region
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "firebase" {
|
||||||
|
project = var.gcp_project_id
|
||||||
|
}
|
||||||
|
|
||||||
|
# Firebase Project Setup
|
||||||
|
resource "google_firebase_project" "default" {
|
||||||
|
provider = google-beta
|
||||||
|
project = var.gcp_project_id
|
||||||
|
}
|
||||||
|
|
||||||
|
# Firebase Web App
|
||||||
|
resource "google_firebase_web_app" "rothbard_portal" {
|
||||||
|
provider = google-beta
|
||||||
|
project = google_firebase_project.default.project
|
||||||
|
display_name = "Rothbard Client Portal"
|
||||||
|
|
||||||
|
app_url = "https://${var.domain_name}"
|
||||||
|
|
||||||
|
# Handle OAuth redirect
|
||||||
|
oauth_config {
|
||||||
|
client_id = var.oauth_client_id
|
||||||
|
client_secret = var.oauth_client_secret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Firestore Database
|
||||||
|
resource "google_firestore_database" "default" {
|
||||||
|
provider = google-beta
|
||||||
|
project = var.gcp_project_id
|
||||||
|
name = "(default)"
|
||||||
|
location_id = var.firestore_location
|
||||||
|
type = "FIRESTORE_NATIVE"
|
||||||
|
|
||||||
|
delete_protection_state = "DISABLED"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Firebase Authentication - Complete Configuration
|
||||||
|
resource "google_identitytoolkit_config" "default" {
|
||||||
|
provider = google-beta
|
||||||
|
project = var.gcp_project_id
|
||||||
|
|
||||||
|
sign_in_options {
|
||||||
|
email {
|
||||||
|
enabled = true
|
||||||
|
password_required = true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Disable other providers for security
|
||||||
|
phone {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
google {
|
||||||
|
enabled = var.enable_google_signin
|
||||||
|
}
|
||||||
|
|
||||||
|
facebook {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
apple {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Email configuration
|
||||||
|
email {
|
||||||
|
reset_password_template {
|
||||||
|
from_email_address = var.auth_from_email
|
||||||
|
from_display_name = var.auth_from_name
|
||||||
|
reply_to = var.auth_reply_to
|
||||||
|
subject = "Reset your Rothbard Law Group password"
|
||||||
|
html = file("${path.module}/templates/reset_password.html")
|
||||||
|
text = file("${path.module}/templates/reset_password.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
email_verification_template {
|
||||||
|
from_email_address = var.auth_from_email
|
||||||
|
from_display_name = var.auth_from_name
|
||||||
|
reply_to = var.auth_reply_to
|
||||||
|
subject = "Verify your Rothbard Law Group account"
|
||||||
|
html = file("${path.module}/templates/email_verification.html")
|
||||||
|
text = file("${path.module}/templates/email_verification.txt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Security settings
|
||||||
|
sign_in {
|
||||||
|
allow_duplicate_emails = false
|
||||||
|
}
|
||||||
|
|
||||||
|
# Multi-factor authentication (disabled for simplicity)
|
||||||
|
multi_factor_auth {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
# Anonymous user access (disabled)
|
||||||
|
anonymous {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Service Account for the Flask App
|
||||||
|
resource "google_service_account" "flask_app" {
|
||||||
|
account_id = "rothbard-flask-app"
|
||||||
|
display_name = "Rothbard Flask App Service Account"
|
||||||
|
}
|
||||||
|
|
||||||
|
# IAM permissions for the Flask App
|
||||||
|
resource "google_project_iam_member" "firestore_access" {
|
||||||
|
project = var.gcp_project_id
|
||||||
|
role = "roles/datastore.user"
|
||||||
|
member = "serviceAccount:${google_service_account.flask_app.email}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_project_iam_member" "firebase_admin" {
|
||||||
|
project = var.gcp_project_id
|
||||||
|
role = "roles/firebase.admin"
|
||||||
|
member = "serviceAccount:${google_service_account.flask_app.email}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Firestore Security Rules
|
||||||
|
resource "google_firestore_security_policy" "default" {
|
||||||
|
project = var.gcp_project_id
|
||||||
|
policy = {
|
||||||
|
rules = [
|
||||||
|
{
|
||||||
|
description = "Allow users to read/write their own profile"
|
||||||
|
match = {
|
||||||
|
collection = "users"
|
||||||
|
document = "{userId}"
|
||||||
|
}
|
||||||
|
allow = [
|
||||||
|
{
|
||||||
|
resource = "read"
|
||||||
|
condition = {
|
||||||
|
name = "request.auth.uid == userId"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resource = "write"
|
||||||
|
condition = {
|
||||||
|
name = "request.auth.uid == userId"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description = "Only authenticated users can access the database"
|
||||||
|
match = {
|
||||||
|
collection = "{collection=**}"
|
||||||
|
}
|
||||||
|
allow = [
|
||||||
|
{
|
||||||
|
resource = "read"
|
||||||
|
condition = {
|
||||||
|
name = "request.auth != null"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resource = "write"
|
||||||
|
condition = {
|
||||||
|
name = "request.auth != null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Firebase Hosting (optional - for static assets)
|
||||||
|
resource "google_firebase_hosting_site" "default" {
|
||||||
|
provider = google-beta
|
||||||
|
project = var.gcp_project_id
|
||||||
|
site_id = "rothbard-portal"
|
||||||
|
|
||||||
|
# Default configuration for hosting
|
||||||
|
config {
|
||||||
|
public_root_dir = "public"
|
||||||
|
headers = [
|
||||||
|
{
|
||||||
|
headers = ["Cache-Control: public, max-age=31536000"]
|
||||||
|
glob = "**/*.@(jpg|jpeg|gif|png|svg|webp)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers = ["Cache-Control: public, max-age=86400"]
|
||||||
|
glob = "**/*.@(css|js)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
redirects = [
|
||||||
|
{
|
||||||
|
status_code = 302
|
||||||
|
path = "/login"
|
||||||
|
location = "/login.html"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
rewrites = [
|
||||||
|
{
|
||||||
|
glob = "**"
|
||||||
|
path = "/index.html"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Output important values
|
||||||
|
output "firebase_web_app_id" {
|
||||||
|
description = "Firebase Web App ID"
|
||||||
|
value = google_firebase_web_app.rothbard_portal.app_id
|
||||||
|
}
|
||||||
|
|
||||||
|
output "firebase_project_id" {
|
||||||
|
description = "Firebase Project ID"
|
||||||
|
value = google_firebase_project.default.project
|
||||||
|
}
|
||||||
|
|
||||||
|
output "service_account_email" {
|
||||||
|
description = "Service account email for Flask app"
|
||||||
|
value = google_service_account.flask_app.email
|
||||||
|
}
|
||||||
118
terraform/modules/app_engine/main.tf
Normal file
118
terraform/modules/app_engine/main.tf
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
# Enable App Engine Admin API
|
||||||
|
resource "google_project_service" "appengine" {
|
||||||
|
project = var.gcp_project_id
|
||||||
|
service = "appengine.googleapis.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
# App Engine Application
|
||||||
|
resource "google_app_engine_application" "app" {
|
||||||
|
project = var.gcp_project_id
|
||||||
|
location_id = var.gcp_region
|
||||||
|
depends_on = [google_project_service.appengine]
|
||||||
|
}
|
||||||
|
|
||||||
|
# App Engine Service for Flask app
|
||||||
|
resource "google_app_engine_standard_app_version" "flask_app" {
|
||||||
|
project = var.gcp_project_id
|
||||||
|
service = "default"
|
||||||
|
version_id = "${var.app_name}-v1"
|
||||||
|
|
||||||
|
runtime = "python311"
|
||||||
|
|
||||||
|
entrypoint {
|
||||||
|
command = "gunicorn -b :$PORT app:app"
|
||||||
|
}
|
||||||
|
|
||||||
|
deployment {
|
||||||
|
zip {
|
||||||
|
source_url = google_storage_bucket_object.app_source_zip.output_uri
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
env_variables = {
|
||||||
|
FLASK_SECRET_KEY = var.flask_secret_key
|
||||||
|
FIREBASE_PROJECT_ID = var.firebase_project_id
|
||||||
|
GOOGLE_APPLICATION_CREDENTIALS = "/etc/secrets/service-account.json"
|
||||||
|
FILEVINE_CLIENT_ID = var.filevine_client_id
|
||||||
|
FILEVINE_CLIENT_SECRET = var.filevine_client_secret
|
||||||
|
FILEVINE_PERSONAL_ACCESS_TOKEN = var.filevine_pat
|
||||||
|
FILEVINE_ORG_ID = var.filevine_org_id
|
||||||
|
FILEVINE_USER_ID = var.filevine_user_id
|
||||||
|
}
|
||||||
|
|
||||||
|
# Service account
|
||||||
|
service_account = var.service_account_email
|
||||||
|
|
||||||
|
# Resources
|
||||||
|
resources {
|
||||||
|
cpu = 1
|
||||||
|
memory_gb = 0.5
|
||||||
|
disk_gb = 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
# Automatic scaling
|
||||||
|
automatic_scaling {
|
||||||
|
min_idle_instances = 0
|
||||||
|
max_idle_instances = 1
|
||||||
|
min_pending_latency = "automatic"
|
||||||
|
max_pending_latency = "automatic"
|
||||||
|
max_concurrent_requests = 80
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
health_check {
|
||||||
|
enable_health_check = true
|
||||||
|
check_path = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
depends_on = [
|
||||||
|
google_storage_bucket_object.app_source_zip,
|
||||||
|
google_secret_manager_secret_version.service_account_key
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make App Engine service publicly accessible
|
||||||
|
resource "google_app_engine_firewall_rule" "allow_all" {
|
||||||
|
project = var.gcp_project_id
|
||||||
|
action = "ALLOW"
|
||||||
|
priority = "1"
|
||||||
|
|
||||||
|
source_range = "*"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cloud Storage bucket for app source code
|
||||||
|
resource "google_storage_bucket" "app_source" {
|
||||||
|
name = "${var.app_name}-source-${var.gcp_project_id}"
|
||||||
|
location = var.gcp_region
|
||||||
|
force_destroy = true
|
||||||
|
|
||||||
|
uniform_bucket_level_access = true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Upload app source code
|
||||||
|
resource "google_storage_bucket_object" "app_source_zip" {
|
||||||
|
name = "app-source.zip"
|
||||||
|
bucket = google_storage_bucket.app_source.name
|
||||||
|
source = var.app_source_zip_path
|
||||||
|
}
|
||||||
|
|
||||||
|
# Store service account key in Secret Manager
|
||||||
|
resource "google_secret_manager_secret" "service_account_key" {
|
||||||
|
project = var.gcp_project_id
|
||||||
|
secret_id = "${var.app_name}-service-account-key"
|
||||||
|
|
||||||
|
replication {
|
||||||
|
automatic = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_secret_manager_secret_version" "service_account_key" {
|
||||||
|
secret = google_secret_manager_secret.service_account_key.id
|
||||||
|
secret_data = var.service_account_key_data
|
||||||
|
}
|
||||||
|
|
||||||
|
# Output the app URL
|
||||||
|
output "app_url" {
|
||||||
|
description = "App Engine application URL"
|
||||||
|
value = "https://${google_app_engine_application.app.default_hostname}"
|
||||||
|
}
|
||||||
71
terraform/modules/app_engine/variables.tf
Normal file
71
terraform/modules/app_engine/variables.tf
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
variable "app_name" {
|
||||||
|
description = "Name of the application"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "gcp_project_id" {
|
||||||
|
description = "GCP Project ID"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "gcp_region" {
|
||||||
|
description = "GCP region"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "app_source_zip_path" {
|
||||||
|
description = "Path to the app source code zip file"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "firebase_project_id" {
|
||||||
|
description = "Firebase project ID"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "flask_secret_key" {
|
||||||
|
description = "Flask secret key"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "service_account_email" {
|
||||||
|
description = "Service account email for the App Engine service"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "service_account_key_data" {
|
||||||
|
description = "Service account key JSON data"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "filevine_client_id" {
|
||||||
|
description = "Filevine client ID"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "filevine_client_secret" {
|
||||||
|
description = "Filevine client secret"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "filevine_pat" {
|
||||||
|
description = "Filevine personal access token"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "filevine_org_id" {
|
||||||
|
description = "Filevine organization ID"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "filevine_user_id" {
|
||||||
|
description = "Filevine user ID"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
144
terraform/modules/cloud_run/main.tf
Normal file
144
terraform/modules/cloud_run/main.tf
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
# Cloud Run Service for Flask App
|
||||||
|
resource "google_cloud_run_service" "flask_app" {
|
||||||
|
name = "${var.app_name}-service"
|
||||||
|
location = var.gcp_region
|
||||||
|
|
||||||
|
template {
|
||||||
|
spec {
|
||||||
|
containers {
|
||||||
|
image = var.container_image
|
||||||
|
|
||||||
|
# Environment variables for the Flask app
|
||||||
|
env {
|
||||||
|
name = "FLASK_SECRET_KEY"
|
||||||
|
value = var.flask_secret_key
|
||||||
|
}
|
||||||
|
|
||||||
|
env {
|
||||||
|
name = "FIREBASE_PROJECT_ID"
|
||||||
|
value = var.firebase_project_id
|
||||||
|
}
|
||||||
|
|
||||||
|
env {
|
||||||
|
name = "GOOGLE_APPLICATION_CREDENTIALS"
|
||||||
|
value = "/etc/secrets/service-account.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Filevine API credentials
|
||||||
|
env {
|
||||||
|
name = "FILEVINE_CLIENT_ID"
|
||||||
|
value = var.filevine_client_id
|
||||||
|
}
|
||||||
|
|
||||||
|
env {
|
||||||
|
name = "FILEVINE_CLIENT_SECRET"
|
||||||
|
value = var.filevine_client_secret
|
||||||
|
}
|
||||||
|
|
||||||
|
env {
|
||||||
|
name = "FILEVINE_PERSONAL_ACCESS_TOKEN"
|
||||||
|
value = var.filevine_pat
|
||||||
|
}
|
||||||
|
|
||||||
|
env {
|
||||||
|
name = "FILEVINE_ORG_ID"
|
||||||
|
value = var.filevine_org_id
|
||||||
|
}
|
||||||
|
|
||||||
|
env {
|
||||||
|
name = "FILEVINE_USER_ID"
|
||||||
|
value = var.filevine_user_id
|
||||||
|
}
|
||||||
|
|
||||||
|
# Memory and CPU limits
|
||||||
|
resources {
|
||||||
|
limits = {
|
||||||
|
cpu = "1000m"
|
||||||
|
memory = "512Mi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mount service account key
|
||||||
|
volume_mount {
|
||||||
|
name = "service-account-key"
|
||||||
|
mount_path = "/etc/secrets"
|
||||||
|
read_only = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Service account for the container
|
||||||
|
service_account_name = var.service_account_email
|
||||||
|
|
||||||
|
# Volumes
|
||||||
|
volumes {
|
||||||
|
name = "service-account-key"
|
||||||
|
secret {
|
||||||
|
secret_name = google_secret_manager_secret.service_account_key.secret_id
|
||||||
|
items {
|
||||||
|
key = "latest"
|
||||||
|
path = "service-account.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Allow unauthenticated access
|
||||||
|
container_concurrency = 100
|
||||||
|
timeout_seconds = 300
|
||||||
|
}
|
||||||
|
|
||||||
|
# Traffic settings
|
||||||
|
metadata {
|
||||||
|
annotations = {
|
||||||
|
"autoscaling.knative.dev/maxScale" = "10"
|
||||||
|
"autoscaling.knative.dev/minScale" = "1"
|
||||||
|
"run.googleapis.com/ingress" = "all"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
traffic {
|
||||||
|
percent = 100
|
||||||
|
latest_revision = true
|
||||||
|
}
|
||||||
|
|
||||||
|
depends_on = [google_secret_manager_secret_version.service_account_key]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make Cloud Run service publicly accessible
|
||||||
|
resource "google_cloud_run_service_iam_member" "public" {
|
||||||
|
location = google_cloud_run_service.flask_app.location
|
||||||
|
project = google_cloud_run_service.flask_app.project
|
||||||
|
service = google_cloud_run_service.flask_app.name
|
||||||
|
role = "roles/run.invoker"
|
||||||
|
member = "allUsers"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Store service account key in Secret Manager
|
||||||
|
resource "google_secret_manager_secret" "service_account_key" {
|
||||||
|
project = var.gcp_project_id
|
||||||
|
secret_id = "${var.app_name}-service-account-key"
|
||||||
|
|
||||||
|
replication {
|
||||||
|
automatic = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_secret_manager_secret_version" "service_account_key" {
|
||||||
|
secret = google_secret_manager_secret.service_account_key.id
|
||||||
|
secret_data = var.service_account_key_data
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cloud Storage bucket for container storage (if needed)
|
||||||
|
resource "google_storage_bucket" "app_storage" {
|
||||||
|
name = "${var.app_name}-storage-${var.gcp_project_id}"
|
||||||
|
location = var.gcp_region
|
||||||
|
force_destroy = true
|
||||||
|
|
||||||
|
uniform_bucket_level_access = true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Output the service URL
|
||||||
|
output "service_url" {
|
||||||
|
description = "Cloud Run service URL"
|
||||||
|
value = google_cloud_run_service.flask_app.status[0].url
|
||||||
|
}
|
||||||
71
terraform/modules/cloud_run/variables.tf
Normal file
71
terraform/modules/cloud_run/variables.tf
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
variable "app_name" {
|
||||||
|
description = "Name of the application"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "gcp_project_id" {
|
||||||
|
description = "GCP Project ID"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "gcp_region" {
|
||||||
|
description = "GCP region"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "container_image" {
|
||||||
|
description = "Docker image for the Flask app"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "firebase_project_id" {
|
||||||
|
description = "Firebase project ID"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "flask_secret_key" {
|
||||||
|
description = "Flask secret key"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "service_account_email" {
|
||||||
|
description = "Service account email for the Cloud Run service"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "service_account_key_data" {
|
||||||
|
description = "Service account key JSON data"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "filevine_client_id" {
|
||||||
|
description = "Filevine client ID"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "filevine_client_secret" {
|
||||||
|
description = "Filevine client secret"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "filevine_pat" {
|
||||||
|
description = "Filevine personal access token"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "filevine_org_id" {
|
||||||
|
description = "Filevine organization ID"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "filevine_user_id" {
|
||||||
|
description = "Filevine user ID"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
95
terraform/templates/email_verification.html
Normal file
95
terraform/templates/email_verification.html
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Verify Your Email - Rothbard Law Group</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: #1a365d;
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 30px 20px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
background: #2c5282;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 30px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
background: #e2e8f0;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.welcome-box {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border-left: 4px solid #2c5282;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>Rothbard Law Group</h1>
|
||||||
|
<p>Client Portal</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<h2>Welcome to the Rothbard Law Group Client Portal</h2>
|
||||||
|
|
||||||
|
<div class="welcome-box">
|
||||||
|
<p><strong>Thank you for signing up!</strong></p>
|
||||||
|
<p>You're just one step away from accessing your case information securely.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Please verify your email address to complete your registration:</p>
|
||||||
|
|
||||||
|
<p><a href="{{ verificationLink }}" class="button">Verify Email Address</a></p>
|
||||||
|
|
||||||
|
<p>Or copy and paste this link into your browser:</p>
|
||||||
|
<p>{{ verificationLink }}</p>
|
||||||
|
|
||||||
|
<p><strong>What happens next?</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>Once verified, you'll have full access to the client portal</li>
|
||||||
|
<li>You can view your case information and documents</li>
|
||||||
|
<li>Communicate securely with our legal team</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><strong>Important:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>This verification link will expire in 24 hours</li>
|
||||||
|
<li>If you didn't create an account, please ignore this email</li>
|
||||||
|
<li>Your account information is kept secure and confidential</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>If you have any questions or need assistance, please contact our support team.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>© 2024 Rothbard Law Group. All rights reserved.</p>
|
||||||
|
<p>This is an automated message. Please do not reply to this email.</p>
|
||||||
|
<p>Rothbard Law Group | 123 Legal Street | City, State 12345 | (555) 123-4567</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
31
terraform/templates/email_verification.txt
Normal file
31
terraform/templates/email_verification.txt
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
Rothbard Law Group - Email Verification
|
||||||
|
|
||||||
|
Welcome to Rothbard Law Group Client Portal!
|
||||||
|
|
||||||
|
Thank you for signing up for our client portal. You're just one step away from accessing your case information securely.
|
||||||
|
|
||||||
|
Please verify your email address to complete your registration:
|
||||||
|
{{ verificationLink }}
|
||||||
|
|
||||||
|
If the button doesn't work, you can copy and paste this link into your browser.
|
||||||
|
|
||||||
|
What happens next?
|
||||||
|
- Once verified, you'll have full access to the client portal
|
||||||
|
- You can view your case information and documents
|
||||||
|
- Communicate securely with our legal team
|
||||||
|
|
||||||
|
Important Information:
|
||||||
|
- This verification link will expire in 24 hours
|
||||||
|
- If you didn't create an account, please ignore this email
|
||||||
|
- Your account information is kept secure and confidential
|
||||||
|
|
||||||
|
If you have any questions or need assistance, please contact our support team.
|
||||||
|
|
||||||
|
Thank you for choosing Rothbard Law Group.
|
||||||
|
|
||||||
|
---
|
||||||
|
Rothbard Law Group
|
||||||
|
123 Legal Street
|
||||||
|
City, State 12345
|
||||||
|
(555) 123-4567
|
||||||
|
support@rothbardlaw.com
|
||||||
78
terraform/templates/reset_password.html
Normal file
78
terraform/templates/reset_password.html
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Reset Your Password - Rothbard Law Group</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: #1a365d;
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 30px 20px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
background: #2c5282;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 30px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
background: #e2e8f0;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>Rothbard Law Group</h1>
|
||||||
|
<p>Client Portal</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<h2>Reset Your Password</h2>
|
||||||
|
|
||||||
|
<p>Hello,</p>
|
||||||
|
|
||||||
|
<p>We received a request to reset the password for your Rothbard Law Group client portal account. Click the button below to reset your password:</p>
|
||||||
|
|
||||||
|
<p><a href="{{ resetLink }}" class="button">Reset Password</a></p>
|
||||||
|
|
||||||
|
<p>Or copy and paste this link into your browser:</p>
|
||||||
|
<p>{{ resetLink }}</p>
|
||||||
|
|
||||||
|
<p><strong>Important:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>This link will expire in 24 hours</li>
|
||||||
|
<li>If you didn't request a password reset, please ignore this email</li>
|
||||||
|
<li>For security reasons, never share this link with anyone</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>If you have any questions or need assistance, please contact our support team.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>© 2024 Rothbard Law Group. All rights reserved.</p>
|
||||||
|
<p>This is an automated message. Please do not reply to this email.</p>
|
||||||
|
<p>Rothbard Law Group | 123 Legal Street | City, State 12345 | (555) 123-4567</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
27
terraform/templates/reset_password.txt
Normal file
27
terraform/templates/reset_password.txt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
Rothbard Law Group - Password Reset
|
||||||
|
|
||||||
|
Hello,
|
||||||
|
|
||||||
|
We received a request to reset your password for your Rothbard Law Group client portal account.
|
||||||
|
|
||||||
|
To reset your password, please visit this link:
|
||||||
|
{{ resetLink }}
|
||||||
|
|
||||||
|
If the button doesn't work, you can copy and paste the link into your browser.
|
||||||
|
|
||||||
|
Important Information:
|
||||||
|
- This link will expire in 24 hours
|
||||||
|
- If you didn't request a password reset, please ignore this email
|
||||||
|
- For security reasons, never share this link with anyone
|
||||||
|
|
||||||
|
If you have any questions or need assistance, please contact our support team.
|
||||||
|
|
||||||
|
Thank you,
|
||||||
|
Rothbard Law Group
|
||||||
|
|
||||||
|
---
|
||||||
|
Rothbard Law Group
|
||||||
|
123 Legal Street
|
||||||
|
City, State 12345
|
||||||
|
(555) 123-4567
|
||||||
|
support@rothbardlaw.com
|
||||||
9
terraform/terraform.tfvars.example
Normal file
9
terraform/terraform.tfvars.example
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Copy this file to terraform.tfvars and fill in your values
|
||||||
|
gcp_project_id = "your-gcp-project-id"
|
||||||
|
domain_name = "rothbard.yourdomain.com"
|
||||||
|
|
||||||
|
# Optional: Override defaults
|
||||||
|
# gcp_region = "us-central1"
|
||||||
|
# firestore_location = "us-central1"
|
||||||
|
# hosting_option = "cloud_run" # Options: cloud_run, app_engine, gcs
|
||||||
|
# environment = "production"
|
||||||
76
terraform/variables.tf
Normal file
76
terraform/variables.tf
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
variable "gcp_project_id" {
|
||||||
|
description = "GCP Project ID for the deployment"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "gcp_region" {
|
||||||
|
description = "GCP region for resources"
|
||||||
|
type = string
|
||||||
|
default = "us-central1"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "firestore_location" {
|
||||||
|
description = "Location for Firestore database"
|
||||||
|
type = string
|
||||||
|
default = "us-central1"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "domain_name" {
|
||||||
|
description = "Domain name for the application"
|
||||||
|
type = string
|
||||||
|
default = "rothbard-portal.example.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "oauth_client_id" {
|
||||||
|
description = "OAuth client ID for Firebase"
|
||||||
|
type = string
|
||||||
|
default = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "oauth_client_secret" {
|
||||||
|
description = "OAuth client secret for Firebase"
|
||||||
|
type = string
|
||||||
|
default = ""
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "hosting_option" {
|
||||||
|
description = "Hosting option for the Flask app"
|
||||||
|
type = string
|
||||||
|
default = "cloud_run"
|
||||||
|
validation {
|
||||||
|
condition = contains(["cloud_run", "app_engine"], var.hosting_option)
|
||||||
|
error_message = "The hosting_option must be one of: cloud_run, app_engine."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "environment" {
|
||||||
|
description = "Environment tag"
|
||||||
|
type = string
|
||||||
|
default = "production"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Firebase Authentication Configuration
|
||||||
|
variable "enable_google_signin" {
|
||||||
|
description = "Enable Google Sign-In authentication provider"
|
||||||
|
type = bool
|
||||||
|
default = false
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "auth_from_email" {
|
||||||
|
description = "From email address for authentication emails"
|
||||||
|
type = string
|
||||||
|
default = "noreply@rothbardlaw.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "auth_from_name" {
|
||||||
|
description = "From display name for authentication emails"
|
||||||
|
type = string
|
||||||
|
default = "Rothbard Law Group"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "auth_reply_to" {
|
||||||
|
description = "Reply-to email address for authentication emails"
|
||||||
|
type = string
|
||||||
|
default = "support@rothbardlaw.com"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user