Add Firestore security rules and deployment scripts
- Add firestore.rules for SMTP config permissions - Add deployment scripts for Firestore rules - Update Admin component with better error handling - Update README with deployment instructions
This commit is contained in:
1 parent
bd6e1eea7c
commit
0f1e8774d7
6 files changed
+204
-4
No files matched your search
@@ -54,6 +54,61 @@ Admin users can access the admin panel by clicking the "Admin" button in the hea
|
||||
|
||||
The admin panel saves settings to Firestore for persistent configuration.
|
||||
|
||||
## Firestore Security Rules
|
||||
|
||||
To enable SMTP configuration saving and other admin features, you need to deploy Firestore security rules:
|
||||
|
||||
1. Install Firebase CLI if you haven't already:
|
||||
```bash
|
||||
npm install -g firebase-tools
|
||||
```
|
||||
|
||||
2. Login to Firebase:
|
||||
```bash
|
||||
firebase login
|
||||
```
|
||||
|
||||
3. Initialize or connect to your Firebase project:
|
||||
```bash
|
||||
firebase use --add
|
||||
```
|
||||
Select your prayer-app project.
|
||||
|
||||
4. Deploy the security rules using the provided script:
|
||||
```bash
|
||||
# On Linux/Mac
|
||||
./deploy-firestore-rules.sh
|
||||
|
||||
# On Windows
|
||||
deploy-firestore-rules.bat
|
||||
```
|
||||
|
||||
Or deploy manually:
|
||||
```bash
|
||||
firebase deploy --only firestore:rules
|
||||
```
|
||||
|
||||
The `firestore.rules` file in this repository allows authenticated users to read/write configuration data needed for the admin panel.
|
||||
|
||||
## SMTP Configuration
|
||||
|
||||
After deploying Firestore rules, you can configure SMTP settings through the admin panel:
|
||||
|
||||
1. Log in as an admin user
|
||||
2. Click the "Admin" button
|
||||
3. Fill in your SMTP provider details (Gmail, Outlook, etc.)
|
||||
4. Save the configuration
|
||||
|
||||
**Gmail Setup:**
|
||||
- Host: `smtp.gmail.com`
|
||||
- Port: `587` (or `465` for SSL)
|
||||
- Username: Your Gmail address
|
||||
- Password: App password (not your regular password)
|
||||
- From: Your Gmail address
|
||||
- Use SSL/TLS: Checked for port 465, unchecked for port 587
|
||||
|
||||
**Note:** For Gmail, you'll need to generate an "App Password" in your Google Account settings.
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, install dependencies:
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
@echo off
|
||||
echo 🚀 Deploying Firestore Security Rules...
|
||||
echo.
|
||||
|
||||
REM Check if Firebase CLI is installed
|
||||
firebase --version >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo ❌ Firebase CLI is not installed. Please install it first:
|
||||
echo npm install -g firebase-tools
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Check if user is logged in
|
||||
firebase projects:list >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo 🔐 Please login to Firebase first:
|
||||
echo firebase login
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo 📋 Available Firebase projects:
|
||||
firebase projects:list
|
||||
echo.
|
||||
echo 🔗 Make sure you're using the correct project (prayer-app-6701f)
|
||||
echo If not, run: firebase use prayer-app-6701f
|
||||
echo.
|
||||
pause
|
||||
|
||||
REM Deploy the rules
|
||||
echo 📤 Deploying Firestore security rules...
|
||||
firebase deploy --only firestore:rules
|
||||
|
||||
if %errorlevel% equ 0 (
|
||||
echo.
|
||||
echo ✅ Firestore security rules deployed successfully!
|
||||
echo You can now save SMTP configuration in the admin panel.
|
||||
) else (
|
||||
echo.
|
||||
echo ❌ Failed to deploy Firestore rules. Please check the error messages above.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
pause
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "🚀 Deploying Firestore Security Rules..."
|
||||
echo ""
|
||||
|
||||
# Check if Firebase CLI is installed
|
||||
if ! command -v firebase &> /dev/null; then
|
||||
echo "❌ Firebase CLI is not installed. Please install it first:"
|
||||
echo "npm install -g firebase-tools"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if user is logged in
|
||||
if ! firebase projects:list &> /dev/null; then
|
||||
echo "🔐 Please login to Firebase first:"
|
||||
echo "firebase login"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📋 Available Firebase projects:"
|
||||
firebase projects:list
|
||||
|
||||
echo ""
|
||||
echo "🔗 Make sure you're using the correct project (prayer-app-6701f)"
|
||||
echo "If not, run: firebase use prayer-app-6701f"
|
||||
echo ""
|
||||
|
||||
read -p "Press Enter to deploy Firestore rules..."
|
||||
|
||||
# Deploy the rules
|
||||
echo "📤 Deploying Firestore security rules..."
|
||||
firebase deploy --only firestore:rules
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo ""
|
||||
echo "✅ Firestore security rules deployed successfully!"
|
||||
echo "You can now save SMTP configuration in the admin panel."
|
||||
else
|
||||
echo ""
|
||||
echo "❌ Failed to deploy Firestore rules. Please check the error messages above."
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,24 @@
|
||||
rules_version = '2';
|
||||
service cloud.firestore {
|
||||
match /databases/{database}/documents {
|
||||
// Allow authenticated users to read/write their own data
|
||||
match /users/{userId} {
|
||||
allow read, write: if request.auth != null && request.auth.uid == userId;
|
||||
}
|
||||
|
||||
// Allow authenticated users to read/write prayers
|
||||
match /prayers/{prayerId} {
|
||||
allow read, write: if request.auth != null;
|
||||
}
|
||||
|
||||
// Allow authenticated users to read/write config (for admin settings)
|
||||
match /config/{configId} {
|
||||
allow read, write: if request.auth != null;
|
||||
}
|
||||
|
||||
// Allow anyone to read public data (if needed in future)
|
||||
match /public/{document} {
|
||||
allow read: if true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
if(!self.define){let e,s={};const a=(a,n)=>(a=new URL(a+".js",n).href,s[a]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=a,e.onload=s,document.head.appendChild(e)}else e=a,importScripts(a),s()}).then(()=>{let e=s[a];if(!e)throw new Error(`Module ${a} didn’t register its module`);return e}));self.define=(n,t)=>{const i=e||("document"in self?document.currentScript.src:"")||location.href;if(s[i])return;let c={};const r=e=>a(e,i),o={module:{uri:i},exports:c,require:r};s[i]=Promise.all(n.map(e=>o[e]||r(e))).then(e=>(t(...e),c))}}define(["./workbox-4754cb34"],function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/static/chunks/139.678015000317a168.js",revision:"678015000317a168"},{url:"/_next/static/chunks/240-754372c10c8766c9.js",revision:"754372c10c8766c9"},{url:"/_next/static/chunks/4bd1b696-b7529f9b306ab05d.js",revision:"b7529f9b306ab05d"},{url:"/_next/static/chunks/512-8d2b9de6fda25175.js",revision:"8d2b9de6fda25175"},{url:"/_next/static/chunks/646.30443a55a43a3bfb.js",revision:"30443a55a43a3bfb"},{url:"/_next/static/chunks/7508b87c-1ab0c72b2a9e57da.js",revision:"1ab0c72b2a9e57da"},{url:"/_next/static/chunks/app/_global-error/page-7a99a1a9afe03223.js",revision:"7a99a1a9afe03223"},{url:"/_next/static/chunks/app/_not-found/page-1123cfe416c748d0.js",revision:"1123cfe416c748d0"},{url:"/_next/static/chunks/app/api/send-email/route-7a99a1a9afe03223.js",revision:"7a99a1a9afe03223"},{url:"/_next/static/chunks/app/layout-dc9d7c3da9cf59ef.js",revision:"dc9d7c3da9cf59ef"},{url:"/_next/static/chunks/app/page-d9fbb10ee3eb8a72.js",revision:"d9fbb10ee3eb8a72"},{url:"/_next/static/chunks/dd8162e8-b0853216adf21a86.js",revision:"b0853216adf21a86"},{url:"/_next/static/chunks/framework-292291387d6b2e39.js",revision:"292291387d6b2e39"},{url:"/_next/static/chunks/main-7ddb690de3906442.js",revision:"7ddb690de3906442"},{url:"/_next/static/chunks/main-app-4f19d69ed4295336.js",revision:"4f19d69ed4295336"},{url:"/_next/static/chunks/next/dist/client/components/builtin/app-error-7a99a1a9afe03223.js",revision:"7a99a1a9afe03223"},{url:"/_next/static/chunks/next/dist/client/components/builtin/forbidden-7a99a1a9afe03223.js",revision:"7a99a1a9afe03223"},{url:"/_next/static/chunks/next/dist/client/components/builtin/global-error-bcba0732671665d2.js",revision:"bcba0732671665d2"},{url:"/_next/static/chunks/next/dist/client/components/builtin/not-found-7a99a1a9afe03223.js",revision:"7a99a1a9afe03223"},{url:"/_next/static/chunks/next/dist/client/components/builtin/unauthorized-7a99a1a9afe03223.js",revision:"7a99a1a9afe03223"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-167d4419e1b22999.js",revision:"167d4419e1b22999"},{url:"/_next/static/css/0e9b93944341831f.css",revision:"0e9b93944341831f"},{url:"/_next/static/hS4u_kF5nc-qn-M9O01uW/_buildManifest.js",revision:"009de90521464c9665092678c4f82fe9"},{url:"/_next/static/hS4u_kF5nc-qn-M9O01uW/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/media/4cf2300e9c8272f7-s.p.woff2",revision:"18bae71b1e1b2bb25321090a3b563103"},{url:"/_next/static/media/747892c23ea88013-s.woff2",revision:"a0761690ccf4441ace5cec893b82d4ab"},{url:"/_next/static/media/8d697b304b401681-s.woff2",revision:"cc728f6c0adb04da0dfcb0fc436a8ae5"},{url:"/_next/static/media/93f479601ee12b01-s.p.woff2",revision:"da83d5f06d825c5ae65b7cca706cb312"},{url:"/_next/static/media/9610d9e46709d722-s.woff2",revision:"7b7c0ef93df188a852344fc272fc096b"},{url:"/_next/static/media/ba015fad6dcf6784-s.woff2",revision:"8ea4f719af3312a055caf09f34c89a77"},{url:"/file.svg",revision:"d09f95206c3fa0bb9bd9fefabfd0ea71"},{url:"/globe.svg",revision:"2aaafa6a49b6563925fe440891e32717"},{url:"/manifest.json",revision:"785fbc5941efd3caa27ade80c67bb5ef"},{url:"/next.svg",revision:"8e061864f388b47f33a1c3780831193e"},{url:"/vercel.svg",revision:"c0af2f507b369b085b35ef4bbe3bcf1e"},{url:"/window.svg",revision:"a2760511c65806022ad20adf74370ff3"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:s,event:a,state:n})=>s&&"opaqueredirect"===s.type?new Response(s.body,{status:200,statusText:"OK",headers:s.headers}):s}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jLine truncated
|
||||
if(!self.define){let e,s={};const a=(a,n)=>(a=new URL(a+".js",n).href,s[a]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=a,e.onload=s,document.head.appendChild(e)}else e=a,importScripts(a),s()}).then(()=>{let e=s[a];if(!e)throw new Error(`Module ${a} didn’t register its module`);return e}));self.define=(n,t)=>{const i=e||("document"in self?document.currentScript.src:"")||location.href;if(s[i])return;let c={};const r=e=>a(e,i),o={module:{uri:i},exports:c,require:r};s[i]=Promise.all(n.map(e=>o[e]||r(e))).then(e=>(t(...e),c))}}define(["./workbox-4754cb34"],function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/static/84QbTf9EqwkRW6ZuK_UKV/_buildManifest.js",revision:"009de90521464c9665092678c4f82fe9"},{url:"/_next/static/84QbTf9EqwkRW6ZuK_UKV/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/139.678015000317a168.js",revision:"678015000317a168"},{url:"/_next/static/chunks/240-754372c10c8766c9.js",revision:"754372c10c8766c9"},{url:"/_next/static/chunks/4bd1b696-b7529f9b306ab05d.js",revision:"b7529f9b306ab05d"},{url:"/_next/static/chunks/512-8d2b9de6fda25175.js",revision:"8d2b9de6fda25175"},{url:"/_next/static/chunks/646.30443a55a43a3bfb.js",revision:"30443a55a43a3bfb"},{url:"/_next/static/chunks/7508b87c-1ab0c72b2a9e57da.js",revision:"1ab0c72b2a9e57da"},{url:"/_next/static/chunks/app/_global-error/page-7a99a1a9afe03223.js",revision:"7a99a1a9afe03223"},{url:"/_next/static/chunks/app/_not-found/page-1123cfe416c748d0.js",revision:"1123cfe416c748d0"},{url:"/_next/static/chunks/app/api/send-email/route-7a99a1a9afe03223.js",revision:"7a99a1a9afe03223"},{url:"/_next/static/chunks/app/layout-dc9d7c3da9cf59ef.js",revision:"dc9d7c3da9cf59ef"},{url:"/_next/static/chunks/app/page-5ebd182a478eec74.js",revision:"5ebd182a478eec74"},{url:"/_next/static/chunks/dd8162e8-b0853216adf21a86.js",revision:"b0853216adf21a86"},{url:"/_next/static/chunks/framework-292291387d6b2e39.js",revision:"292291387d6b2e39"},{url:"/_next/static/chunks/main-7ddb690de3906442.js",revision:"7ddb690de3906442"},{url:"/_next/static/chunks/main-app-4f19d69ed4295336.js",revision:"4f19d69ed4295336"},{url:"/_next/static/chunks/next/dist/client/components/builtin/app-error-7a99a1a9afe03223.js",revision:"7a99a1a9afe03223"},{url:"/_next/static/chunks/next/dist/client/components/builtin/forbidden-7a99a1a9afe03223.js",revision:"7a99a1a9afe03223"},{url:"/_next/static/chunks/next/dist/client/components/builtin/global-error-bcba0732671665d2.js",revision:"bcba0732671665d2"},{url:"/_next/static/chunks/next/dist/client/components/builtin/not-found-7a99a1a9afe03223.js",revision:"7a99a1a9afe03223"},{url:"/_next/static/chunks/next/dist/client/components/builtin/unauthorized-7a99a1a9afe03223.js",revision:"7a99a1a9afe03223"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-167d4419e1b22999.js",revision:"167d4419e1b22999"},{url:"/_next/static/css/8a0dbbd40cc7c3db.css",revision:"8a0dbbd40cc7c3db"},{url:"/_next/static/media/4cf2300e9c8272f7-s.p.woff2",revision:"18bae71b1e1b2bb25321090a3b563103"},{url:"/_next/static/media/747892c23ea88013-s.woff2",revision:"a0761690ccf4441ace5cec893b82d4ab"},{url:"/_next/static/media/8d697b304b401681-s.woff2",revision:"cc728f6c0adb04da0dfcb0fc436a8ae5"},{url:"/_next/static/media/93f479601ee12b01-s.p.woff2",revision:"da83d5f06d825c5ae65b7cca706cb312"},{url:"/_next/static/media/9610d9e46709d722-s.woff2",revision:"7b7c0ef93df188a852344fc272fc096b"},{url:"/_next/static/media/ba015fad6dcf6784-s.woff2",revision:"8ea4f719af3312a055caf09f34c89a77"},{url:"/file.svg",revision:"d09f95206c3fa0bb9bd9fefabfd0ea71"},{url:"/globe.svg",revision:"2aaafa6a49b6563925fe440891e32717"},{url:"/manifest.json",revision:"785fbc5941efd3caa27ade80c67bb5ef"},{url:"/next.svg",revision:"8e061864f388b47f33a1c3780831193e"},{url:"/vercel.svg",revision:"c0af2f507b369b085b35ef4bbe3bcf1e"},{url:"/window.svg",revision:"a2760511c65806022ad20adf74370ff3"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:s,event:a,state:n})=>s&&"opaqueredirect"===s.type?new Response(s.body,{status:200,statusText:"OK",headers:s.headers}):s}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jLine truncated
|
||||
@@ -64,14 +64,42 @@ export default function Admin() {
|
||||
};
|
||||
|
||||
const saveConfig = async () => {
|
||||
// Validate required fields
|
||||
if (!config.host.trim()) {
|
||||
setMessage('SMTP host is required.');
|
||||
return;
|
||||
}
|
||||
if (!config.user.trim()) {
|
||||
setMessage('SMTP username is required.');
|
||||
return;
|
||||
}
|
||||
if (!config.pass.trim()) {
|
||||
setMessage('SMTP password is required.');
|
||||
return;
|
||||
}
|
||||
if (!config.from.trim()) {
|
||||
setMessage('From email is required.');
|
||||
return;
|
||||
}
|
||||
if (isNaN(config.port) || config.port < 1 || config.port > 65535) {
|
||||
setMessage('Port must be a valid number between 1 and 65535.');
|
||||
return;
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
setMessage('');
|
||||
try {
|
||||
await setDoc(doc(db, 'config', 'smtp'), config);
|
||||
setMessage('SMTP configuration saved successfully!');
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error('Error saving config:', error);
|
||||
setMessage('Error saving configuration.');
|
||||
if (error.code === 'permission-denied') {
|
||||
setMessage('Permission denied. Please check your authentication and Firestore security rules.');
|
||||
} else if (error.code === 'unavailable') {
|
||||
setMessage('Firestore is currently unavailable. Please try again later.');
|
||||
} else {
|
||||
setMessage(`Error saving configuration: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
@@ -159,8 +187,13 @@ export default function Admin() {
|
||||
<input
|
||||
type="number"
|
||||
value={config.port}
|
||||
onChange={(e) => setConfig(prev => ({ ...prev, port: parseInt(e.target.value) }))}
|
||||
onChange={(e) => {
|
||||
const value = parseInt(e.target.value);
|
||||
setConfig(prev => ({ ...prev, port: isNaN(value) ? 587 : value }));
|
||||
}}
|
||||
className="w-full p-2 bg-gray-700 border border-gray-600 rounded text-white"
|
||||
min="1"
|
||||
max="65535"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in new issue
Block a user