strapi-plugin-firebase-authentication 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,93 +4,67 @@
4
4
  [![npm downloads](https://img.shields.io/npm/dm/strapi-plugin-firebase-authentication.svg)](https://www.npmjs.com/package/strapi-plugin-firebase-authentication)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
- A production-ready Strapi v5 plugin that seamlessly integrates Firebase Authentication with your Strapi Headless CMS. Authenticate users via Firebase (Google, Apple, Email/Password, Phone, Magic Link) and automatically sync them with Strapi's user system.
8
-
9
- ## Features at a Glance
10
-
11
- - **Multiple Authentication Methods**: Google Sign-In, Apple Sign-In, Email/Password, Phone-only, Magic Link (passwordless)
12
- - **Automatic User Sync**: Creates and updates Strapi users from Firebase authentication
13
- - **Password Reset Flow**: Complete password reset with email verification
14
- - **Phone-Only Support**: Configurable email handling for phone-based authentication
15
- - **Admin Panel**: Manage Firebase users directly from Strapi admin
16
- - **Secure Configuration**: AES-256 encrypted Firebase credentials
17
- - **Email Service**: Three-tier fallback (Strapi Email Plugin → Firebase Extension → Console)
18
- - **Flexible User Lookup**: Multiple strategies (Firebase UID, email, phone, Apple relay email)
19
-
20
- ## Table of Contents
21
-
22
- 1. [Quick Reference](#quick-reference)
23
- 2. [Installation](#installation)
24
- 3. [Configuration](#configuration)
25
- 4. [Features & Authentication Flows](#features--authentication-flows)
26
- 5. [API Reference](#api-reference)
27
- 6. [Admin Panel](#admin-panel)
28
- 7. [Client Integration](#client-integration)
29
- 8. [Email Templates](#email-templates)
30
- 9. [Architecture & Database](#architecture--database)
31
- 10. [Security](#security)
32
- 11. [Troubleshooting](#troubleshooting)
33
- 12. [Best Practices](#best-practices)
34
- 13. [Support](#support)
35
-
36
- ## Quick Reference
37
-
38
- ### Essential Endpoints
39
-
40
- **Authentication:**
41
-
42
- - `POST /api/firebase-authentication` - Exchange Firebase token for Strapi JWT
43
- - `POST /api/firebase-authentication/emailLogin` - Direct email/password login
44
- - `POST /api/firebase-authentication/forgotPassword` - Request password reset
45
- - `POST /api/firebase-authentication/resetPassword` - Authenticated password change (requires JWT)
46
- - `POST /api/firebase-authentication/requestMagicLink` - Passwordless login
47
- - `GET /api/firebase-authentication/config` - Get public configuration
48
-
49
- ### Minimal Configuration
50
-
51
- ```javascript
52
- // config/plugins.js
53
- module.exports = () => ({
54
- "firebase-authentication": {
55
- enabled: true,
56
- config: {
57
- FIREBASE_JSON_ENCRYPTION_KEY: process.env.FIREBASE_JSON_ENCRYPTION_KEY,
58
- },
59
- },
60
- });
7
+ **Use Firebase Authentication with Strapi v5** - without building custom auth logic.
8
+
9
+ Firebase handles user authentication (Google, Apple, Phone, Email, Magic Link). This plugin bridges Firebase to Strapi by verifying tokens, automatically creating/syncing users, and returning Strapi JWTs for API access. Your users authenticate once with Firebase and get full access to your Strapi content based on roles and permissions.
10
+
11
+ ## How It Works
12
+
13
+ ```mermaid
14
+ sequenceDiagram
15
+ participant App as Your App
16
+ participant Firebase as Firebase Auth
17
+ participant Strapi as Strapi + Plugin
18
+
19
+ rect rgb(240, 248, 255)
20
+ Note over App,Firebase: Authentication
21
+ App->>Firebase: 1. Sign in (Google, Apple, Phone, Email...)
22
+ Firebase-->>App: 2. Returns Firebase ID Token
23
+ end
24
+
25
+ rect rgb(240, 255, 240)
26
+ Note over App,Strapi: Token Exchange
27
+ App->>Strapi: 3. POST /api/firebase-authentication { idToken }
28
+ Strapi->>Firebase: 4. Verify token
29
+ Firebase-->>Strapi: 5. Token valid
30
+ Note over Strapi: 6. Find or create Strapi user
31
+ Strapi-->>App: 7. Returns { user, jwt }
32
+ end
33
+
34
+ rect rgb(255, 250, 240)
35
+ Note over App,Strapi: API Access
36
+ App->>Strapi: 8. GET /api/content (Bearer jwt)
37
+ Strapi-->>App: 9. Protected data
38
+ end
61
39
  ```
62
40
 
63
- ```bash
64
- # .env
65
- FIREBASE_JSON_ENCRYPTION_KEY=your-secure-32-character-key-here
66
- ```
41
+ **Summary:**
42
+
43
+ - **Steps 1-2:** User authenticates with Firebase (you handle this with Firebase SDK)
44
+ - **Steps 3-7:** You send the Firebase token to this plugin, it verifies with Firebase, finds or creates a Strapi user, and returns a Strapi JWT
45
+ - **Steps 8-9:** You use the Strapi JWT for all API calls to access protected content
46
+
47
+ ## Features
67
48
 
68
- ### Required Setup Steps
49
+ - 🔐 **Multi-Provider Auth** - Support for Google, Apple, Email/Password, Phone, and Magic Link. All providers are verified server-side via Firebase Admin SDK and mapped to a single Strapi user using Firebase UID as the primary link. User signs in with Google on mobile, later with Apple on web - same Strapi user, same roles and permissions.
69
50
 
70
- 1. **Install:** `yarn add strapi-plugin-firebase-authentication`
71
- 2. **Configure:** Add plugin config to `config/plugins.js`
72
- 3. **Build:** `yarn build && yarn develop`
73
- 4. **Upload:** Settings → Firebase Authentication → Upload service account JSON
74
- 5. **Permissions:** Settings → Users & Permissions → Public → `firebase-authentication.authenticate` ✓
51
+ - 🔄 **Auto User Sync** - On first authentication, the plugin automatically creates a Strapi user with the default `authenticated` role. Existing users are matched by Firebase UID first, then by email, then by phone number. Profile data (name, email, phone) is synced from the Firebase token. No manual user creation needed - scales to thousands of users.
75
52
 
76
- ### Admin Access
53
+ - 📱 **Phone-Only Auth** - For apps where email isn't required (common in emerging markets), the plugin generates unique placeholder emails using configurable patterns with tokens: `{randomString}`, `{phoneNumber}`, `{timestamp}`. Example: `{randomString}@phone-user.local` becomes `a1b2c3d4@phone-user.local`. Phone users get full access to Strapi's permission system.
77
54
 
78
- - **Settings:** Settings Firebase Authentication
79
- - **User Management:** Plugins → Firebase Authentication
55
+ - 🔑 **Password Reset** - Two flows available: `POST /forgotPassword` sends a branded reset email via your Strapi email provider (SendGrid, Mailgun, etc.), and `POST /resetPassword` allows authenticated password changes. Includes customizable email templates, configurable reset URLs, regex password validation, and optional Firebase custom tokens for auto-login after reset.
80
56
 
81
- ---
57
+ - ✨ **Magic Link** - Passwordless authentication via `POST /requestMagicLink`. Generates a secure one-time JWT token with configurable expiry (1-72 hours), sends via your Strapi email provider, and returns a Strapi JWT on verification. Tokens are invalidated after use. Perfect for B2B apps where users prefer "email me a login link" over passwords.
82
58
 
83
- ## Installation
59
+ - 🛡️ **Encrypted Config** - Firebase service account JSON (contains private keys) is encrypted with AES-256 using your `FIREBASE_JSON_ENCRYPTION_KEY` before storing in the database. Decrypted only in memory at runtime - never exposed in API responses, logs, or database backups. Meets enterprise security requirements.
84
60
 
85
- ### Prerequisites
61
+ - 📊 **Activity Logging** - Tracks all authentication events with full context: user ID, Firebase UID, action type (login, token exchange, password reset, account deletion), IP address, and timestamp. Automatic cleanup via `FIREBASE_ACTIVITY_LOG_RETENTION_DAYS`. Essential for security audits, debugging auth issues, and compliance requirements.
86
62
 
87
- Before installing, ensure you have:
63
+ - 🎛️ **Admin Panel** - Full Firebase user management UI integrated into Strapi admin at Plugins > Firebase Authentication. Search users by email/phone/UID, view linked Strapi accounts, edit user details, trigger password reset emails, send verification emails, and delete users from Firebase, Strapi, or both. No need to switch to Firebase Console.
88
64
 
89
- - Strapi v5 project (this plugin is for v5 only)
90
- - Firebase project with Authentication enabled ([Create one](https://console.firebase.google.com/))
91
- - Node.js 18+ and npm/yarn installed
65
+ ## Quick Start
92
66
 
93
- ### Step 1: Install Plugin
67
+ ### 1. Install
94
68
 
95
69
  ```bash
96
70
  yarn add strapi-plugin-firebase-authentication
@@ -98,26 +72,9 @@ yarn add strapi-plugin-firebase-authentication
98
72
  npm install strapi-plugin-firebase-authentication
99
73
  ```
100
74
 
101
- **Verify:** Check that the plugin appears in your `package.json` dependencies.
102
-
103
- ---
104
-
105
- ### Step 2: Create Encryption Key
106
-
107
- Generate a secure 32+ character encryption key for storing Firebase credentials:
108
-
109
- ```bash
110
- # Generate a random key (save this!)
111
- node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
112
- ```
113
-
114
- **Common Mistake:** Using a weak or short key. The key MUST be at least 32 characters.
115
-
116
- ---
75
+ ### 2. Configure
117
76
 
118
- ### Step 3: Configure Plugin
119
-
120
- Create or edit `config/plugins.js`:
77
+ Create or update `config/plugins.js`:
121
78
 
122
79
  ```javascript
123
80
  module.exports = () => ({
@@ -130,297 +87,212 @@ module.exports = () => ({
130
87
  });
131
88
  ```
132
89
 
133
- Add to `.env` file:
90
+ Add to `.env`:
134
91
 
135
92
  ```bash
136
- FIREBASE_JSON_ENCRYPTION_KEY=your-generated-key-from-step-2
93
+ # Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
94
+ FIREBASE_JSON_ENCRYPTION_KEY=your-32-character-minimum-key-here
137
95
  ```
138
96
 
139
- **Verify:** Run `echo $FIREBASE_JSON_ENCRYPTION_KEY` to confirm it's set.
140
-
141
- ---
142
-
143
- ### Step 4: Build and Start
97
+ ### 3. Build & Start
144
98
 
145
99
  ```bash
146
- yarn build
147
- yarn develop
148
- ```
149
-
150
- **What happens:**
151
-
152
- - Plugin compiles (admin + server)
153
- - Strapi restarts with plugin enabled
154
- - "Firebase Authentication" appears in Plugins sidebar
155
-
156
- **Verify:** Check console output for:
157
-
158
- ```
159
- ✔ Building admin panel (XX.Xs)
160
- Firebase Authentication plugin initialized
100
+ yarn build && yarn develop
161
101
  ```
162
102
 
163
- **If build fails:** Run `yarn build --clean` to clear cache.
103
+ ### 4. Upload Firebase Config
164
104
 
165
- ---
105
+ 1. Go to [Firebase Console](https://console.firebase.google.com/) → Project Settings → Service Accounts
106
+ 2. Click **Generate New Private Key** (downloads JSON)
107
+ 3. In Strapi: **Settings → Firebase Authentication → Upload Configuration**
108
+ 4. Restart Strapi after uploading
166
109
 
167
- ### Step 5: Download Firebase Service Account
110
+ ### 5. Enable Permissions
168
111
 
169
- 1. Go to [Firebase Console](https://console.firebase.google.com/)
170
- 2. Select your project
171
- 3. Navigate to: **Project Settings** (⚙️ icon) → **Service Accounts** tab
172
- 4. Click **"Generate New Private Key"**
173
- 5. Download and save the JSON file securely (you'll upload this next)
112
+ **Settings Users & Permissions → Roles → Public**
174
113
 
175
- **Important:** This JSON contains sensitive credentials. Never commit it to Git.
114
+ Enable: `firebase-authentication` `authenticate`
176
115
 
177
- ---
116
+ ## API Reference
178
117
 
179
- ### Step 6: Upload to Strapi
118
+ ### Authentication Endpoints
180
119
 
181
- 1. Navigate to: **Settings → Firebase Authentication** (left sidebar)
182
- 2. Click **"Upload Configuration"** button
183
- 3. Select the downloaded service account JSON file
184
- 4. Wait for "Configuration uploaded successfully" message
185
- 5. **Restart Strapi:** `yarn develop` (important!)
120
+ | Method | Endpoint | Purpose |
121
+ | ------ | ----------------------------------------------- | -------------------------------------- |
122
+ | POST | `/api/firebase-authentication` | Exchange Firebase token for Strapi JWT |
123
+ | POST | `/api/firebase-authentication/emailLogin` | Direct email/password login |
124
+ | POST | `/api/firebase-authentication/forgotPassword` | Request password reset email |
125
+ | POST | `/api/firebase-authentication/requestMagicLink` | Request passwordless login link |
126
+ | GET | `/api/firebase-authentication/config` | Get public configuration |
186
127
 
187
- **Verify:** You should see in console:
128
+ ### Token Exchange (Main Endpoint)
188
129
 
130
+ ```bash
131
+ POST /api/firebase-authentication
132
+ Content-Type: application/json
133
+
134
+ {
135
+ "idToken": "firebase-id-token-here",
136
+ "profileMetaData": { // Optional
137
+ "firstName": "John",
138
+ "lastName": "Doe"
139
+ }
140
+ }
189
141
  ```
190
- Firebase Admin SDK initialized successfully
191
- ```
192
-
193
- **If initialization fails:** Check [Troubleshooting](#troubleshooting) section.
194
-
195
- ---
196
-
197
- ### Step 7: Configure Permissions
198
142
 
199
- Navigate to: **Settings → Users & Permissions → Roles → Public**
143
+ **Response:**
200
144
 
201
- Enable these permissions:
202
-
203
- - `firebase-authentication` → `authenticate` ✓
204
-
205
- **Why:** This allows unauthenticated users to exchange Firebase tokens for Strapi JWTs.
206
-
207
- **Verify:** The permission checkbox should be checked and saved.
208
-
209
- ---
210
-
211
- ### Step 8: Test Your Setup
212
-
213
- Create a simple test to verify everything works:
214
-
215
- **Option 1: Test with Firebase Token**
216
-
217
- 1. Get a Firebase ID token from your client app (or Firebase Console)
218
- 2. Send POST request to: `http://localhost:1337/api/firebase-authentication`
219
- 3. Body: `{ "idToken": "your-firebase-token-here" }`
220
- 4. Expected: `200 OK` with `{ user, jwt }` response
221
-
222
- **Option 2: Test with Email/Password** (if configured)
223
-
224
- 1. Create a user in Firebase Console
225
- 2. Send POST to: `http://localhost:1337/api/firebase-authentication/emailLogin`
226
- 3. Body: `{ "email": "test@example.com", "password": "password123" }`
227
- 4. Expected: `200 OK` with `{ user, jwt }` response
228
-
229
- **If tests fail:** Check [Troubleshooting](#troubleshooting) for common issues.
230
-
231
- ---
232
-
233
- ### Common Setup Mistakes
234
-
235
- ❌ **Encryption key too short** → Must be 32+ characters
236
- ❌ **Forgot to restart after uploading config** → Always restart Strapi
237
- ❌ **Wrong Firebase project** → Ensure service account matches your client app
238
- ❌ **Forgot to enable permissions** → Public role needs `authenticate` permission
239
- ❌ **Committed service account JSON to Git** → Use `.gitignore`
240
-
241
- ---
145
+ ```json
146
+ {
147
+ "user": {
148
+ "id": 1,
149
+ "documentId": "abc123",
150
+ "email": "user@example.com",
151
+ "username": "user"
152
+ },
153
+ "jwt": "strapi-jwt-token"
154
+ }
155
+ ```
242
156
 
243
- ### Next Steps
157
+ ## Client Integration
244
158
 
245
- After successful installation:
159
+ ```javascript
160
+ import { getAuth } from "firebase/auth";
246
161
 
247
- 1. **Configure additional settings** (optional):
248
- - Password requirements: **Settings → Firebase Authentication**
249
- - Magic link settings (passwordless auth)
250
- - Email templates for password reset
162
+ // After Firebase sign-in
163
+ const auth = getAuth();
164
+ const idToken = await auth.currentUser.getIdToken();
251
165
 
252
- 2. **Integrate with your client app** (see [Client Integration](#client-integration))
166
+ // Exchange for Strapi JWT
167
+ const response = await fetch("https://your-api.com/api/firebase-authentication", {
168
+ method: "POST",
169
+ headers: { "Content-Type": "application/json" },
170
+ body: JSON.stringify({ idToken }),
171
+ });
253
172
 
254
- 3. **Set up email service** for password reset (see [Email Templates](#email-templates))
173
+ const { user, jwt } = await response.json();
255
174
 
256
- 4. **Review security best practices** (see [Best Practices](#best-practices))
175
+ // Use JWT for Strapi API calls
176
+ const content = await fetch("https://your-api.com/api/articles", {
177
+ headers: { Authorization: `Bearer ${jwt}` },
178
+ });
179
+ ```
257
180
 
258
181
  ## Configuration
259
182
 
260
- The plugin is configured in two places: `config/plugins.js` and the Strapi admin panel.
261
-
262
- **Minimal Configuration** (`config/plugins.js`):
183
+ ### Plugin Config (`config/plugins.js`)
263
184
 
264
185
  ```javascript
265
186
  module.exports = () => ({
266
187
  "firebase-authentication": {
267
188
  enabled: true,
268
189
  config: {
190
+ // Required: Key used to encrypt Firebase credentials (min 32 characters)
269
191
  FIREBASE_JSON_ENCRYPTION_KEY: process.env.FIREBASE_JSON_ENCRYPTION_KEY,
270
- },
271
- },
272
- });
273
- ```
274
-
275
- **Admin Panel Settings** (Settings → Firebase Authentication):
276
-
277
- - Firebase Web API Key (for email/password login)
278
- - Password requirements (regex + message)
279
- - Password reset URL & email subject
280
- - Magic link settings (enable, URL, subject, expiry)
281
- - Phone-only user handling (`emailRequired: false` for phone-only apps)
282
-
283
- ## API Reference
284
-
285
- ### Public Endpoints
286
-
287
- | Method | Endpoint | Purpose |
288
- | ------ | ----------------------------------------------- | -------------------------------------------- |
289
- | POST | `/api/firebase-authentication` | Exchange Firebase token for Strapi JWT |
290
- | POST | `/api/firebase-authentication/emailLogin` | Email/password login (no SDK required) |
291
- | POST | `/api/firebase-authentication/forgotPassword` | Request password reset email |
292
- | POST | `/api/firebase-authentication/resetPassword` | Authenticated password change (requires JWT) |
293
- | POST | `/api/firebase-authentication/requestMagicLink` | Request passwordless login email |
294
- | GET | `/api/firebase-authentication/config` | Get public configuration |
295
-
296
- ### Password Reset Flow
297
-
298
- There are two distinct password reset approaches in this plugin:
299
192
 
300
- #### 1. Forgot Password Flow (Email-Based)
193
+ // Optional: Require email for all users (default: false)
194
+ // When false, phone-only users get auto-generated emails
195
+ emailRequired: false,
301
196
 
302
- **For users who forgot their password:**
197
+ // Optional: Email pattern for phone-only users
198
+ // Tokens: {randomString}, {phoneNumber}, {timestamp}
199
+ emailPattern: "{randomString}@phone-user.firebase.local",
303
200
 
304
- 1. User requests reset: `POST /api/firebase-authentication/forgotPassword` with `{ "email": "user@example.com" }`
305
- 2. Firebase sends email with link to Firebase's hosted password reset page
306
- 3. User clicks link → Opens Firebase's secure hosted UI
307
- 4. User enters new password on Firebase's page
308
- 5. After success → Redirects to configured continue URL
309
- 6. User logs in normally with new password
310
-
311
- **Configuration:** Set `passwordResetUrl` in Firebase Authentication settings (this is where users land AFTER resetting their password on Firebase's page).
312
-
313
- #### 2. Authenticated Password Change
314
-
315
- **For admin-initiated resets or users changing their own password:**
316
-
317
- - **Endpoint:** `POST /api/firebase-authentication/resetPassword`
318
- - **Requires:** Valid JWT in `Authorization` header + `{ "password": "newpassword" }` in body
319
- - **Use cases:**
320
- - Admin resetting a user's password via admin panel
321
- - Authenticated user changing their own password
322
- - **Returns:** Updated user object + fresh JWT for auto-login
323
-
324
- **Note:** This endpoint is NOT part of the forgot password email flow. Use `forgotPassword` for email-based password reset.
325
-
326
- ### Admin Endpoints
327
-
328
- Admin endpoints use the admin API type (no `/api` prefix) and require admin authentication.
329
-
330
- **User Management:**
331
- | Method | Endpoint | Purpose |
332
- |--------|----------|---------|
333
- | GET | `/firebase-authentication/users` | List/search users |
334
- | POST | `/firebase-authentication/users` | Create user |
335
- | GET | `/firebase-authentication/users/:id` | Get user |
336
- | PUT | `/firebase-authentication/users/:id` | Update user |
337
- | DELETE | `/firebase-authentication/users/:id` | Delete user |
338
- | PUT | `/firebase-authentication/users/resetPassword/:id` | Reset password |
339
-
340
- **Settings Management:**
341
- | Method | Endpoint | Purpose |
342
- |--------|----------|---------|
343
- | GET | `/firebase-authentication/settings/firebase-config` | Get Firebase config |
344
- | POST | `/firebase-authentication/settings/firebase-config` | Upload Firebase config |
345
- | DELETE | `/firebase-authentication/settings/firebase-config` | Delete Firebase config |
346
- | POST | `/firebase-authentication/settings/password-config` | Update password/magic link settings |
347
-
348
- ---
349
-
350
- ## Usage
351
-
352
- **Basic Flow:**
353
-
354
- 1. User authenticates with Firebase Client SDK
355
- 2. Client gets Firebase ID token
356
- 3. Client sends token to Strapi: `POST /api/firebase-authentication`
357
- 4. Plugin returns Strapi JWT for API access
358
-
359
- **Example (JavaScript):**
360
-
361
- ```javascript
362
- // After Firebase authentication
363
- const idToken = await firebaseUser.getIdToken();
364
-
365
- // Exchange with Strapi
366
- const response = await fetch("https://your-api.com/api/firebase-authentication", {
367
- method: "POST",
368
- headers: { "Content-Type": "application/json" },
369
- body: JSON.stringify({ idToken }),
201
+ // Optional: Days to keep activity logs (default: null = forever)
202
+ activityLogRetentionDays: 90,
203
+ },
204
+ },
370
205
  });
371
-
372
- const { user, jwt } = await response.json();
373
- localStorage.setItem("jwt", jwt); // Use this JWT for Strapi API calls
374
206
  ```
375
207
 
376
- **Resources:**
377
-
378
- - [Firebase Web SDK](https://firebase.google.com/docs/auth/web/start)
379
- - [Firebase iOS SDK](https://firebase.google.com/docs/auth/ios/start)
380
- - [Firebase Android SDK](https://firebase.google.com/docs/auth/android/start)
381
-
382
- ---
208
+ ### Environment Variables
209
+
210
+ | Variable | Required | Description |
211
+ | -------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
212
+ | `FIREBASE_JSON_ENCRYPTION_KEY` | Yes | AES encryption key for Firebase credentials. Minimum 32 characters. Generate with: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"` |
213
+ | `FIREBASE_EMAIL_REQUIRED` | No | Set to `true` to require email for all users. Default: `false` |
214
+ | `FIREBASE_ACTIVITY_LOG_RETENTION_DAYS` | No | Auto-delete logs older than N days. Default: `null` (keep forever) |
215
+
216
+ ### Admin Panel Settings
217
+
218
+ Configure in **Settings → Firebase Authentication**:
219
+
220
+ **General**
221
+ | Setting | Default | Description |
222
+ |---------|---------|-------------|
223
+ | Firebase Web API Key | - | Required for `emailLogin` endpoint. Get from Firebase Console → Project Settings → General |
224
+
225
+ **Password Settings**
226
+ | Setting | Default | Description |
227
+ |---------|---------|-------------|
228
+ | Password Requirements Regex | `^.{6,}$` | Regex pattern for password validation (default: 6+ chars) |
229
+ | Password Requirements Message | "Password must be at least 6 characters long" | Error message shown when password doesn't match regex |
230
+ | Password Reset URL | `http://localhost:3000/reset-password` | URL where users land after resetting password |
231
+ | Password Reset Email Subject | "Reset Your Password" | Subject line for password reset emails |
232
+ | Include Credentials in Reset Link | `false` | Include Firebase custom token for auto-login after reset |
233
+
234
+ **Magic Link (Passwordless)**
235
+ | Setting | Default | Description |
236
+ |---------|---------|-------------|
237
+ | Enable Magic Link | `false` | Toggle passwordless email authentication |
238
+ | Magic Link URL | `http://localhost:1338/verify-magic-link.html` | Landing page for magic link clicks |
239
+ | Magic Link Email Subject | "Sign in to Your Application" | Subject line for magic link emails |
240
+ | Magic Link Expiry Hours | `1` | Token validity (1-72 hours) |
241
+
242
+ **Email Verification**
243
+ | Setting | Default | Description |
244
+ |---------|---------|-------------|
245
+ | Email Verification URL | `http://localhost:3000/verify-email` | URL for email verification redirect |
246
+ | Email Verification Subject | "Verify Your Email" | Subject line for verification emails |
247
+ | Include Credentials in Verification Link | `false` | Include Firebase custom token for auto-login after verification |
248
+
249
+ ### Firebase Service Account
250
+
251
+ The service account JSON (uploaded via admin panel) should contain:
252
+
253
+ ```json
254
+ {
255
+ "type": "service_account",
256
+ "project_id": "your-project-id",
257
+ "private_key_id": "...",
258
+ "private_key": "-----BEGIN PRIVATE KEY-----\n...",
259
+ "client_email": "firebase-adminsdk-xxxxx@your-project.iam.gserviceaccount.com",
260
+ "client_id": "...",
261
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
262
+ "token_uri": "https://oauth2.googleapis.com/token"
263
+ }
264
+ ```
383
265
 
384
- ## Architecture
266
+ Get this from: [Firebase Console](https://console.firebase.google.com/) → Project Settings → Service Accounts → Generate New Private Key
385
267
 
386
- The plugin validates Firebase ID tokens and syncs users between Firebase and Strapi. Users authenticate via Firebase on the client, then exchange their Firebase token for a Strapi JWT to access your API.
268
+ ## Admin Panel
387
269
 
388
- **Security:**
270
+ Access at **Plugins → Firebase Authentication**:
389
271
 
390
- - Firebase service account JSON encrypted with AES-256
391
- - All tokens validated server-side via Firebase Admin SDK
392
- - Passwords managed by Firebase (not Strapi)
393
- - User responses automatically sanitized
272
+ - View and search Firebase users
273
+ - Edit user details
274
+ - Delete users (from Firebase, Strapi, or both)
275
+ - Send password reset emails
276
+ - View activity logs
394
277
 
395
278
  ## Troubleshooting
396
279
 
397
- ### 🔴 "Firebase is not initialized"
398
-
399
- **Solution:**
280
+ ### "Firebase is not initialized"
400
281
 
401
- 1. Verify `FIREBASE_JSON_ENCRYPTION_KEY` in `config/plugins.js` (min 32 characters)
402
- 2. Upload Firebase service account JSON: **Settings Firebase Authentication**
403
- 3. Restart Strapi: `yarn develop`
404
- 4. Check startup logs for initialization errors
282
+ 1. Check `FIREBASE_JSON_ENCRYPTION_KEY` is set (minimum 32 characters)
283
+ 2. Upload Firebase service account JSON via admin panel
284
+ 3. Restart Strapi after uploading config
285
+ 4. Check console for initialization errors
405
286
 
406
- ---
287
+ ### "Token validation failed"
407
288
 
408
- ### 🔴 "Token validation failed"
289
+ 1. Token expired (1 hour TTL) - get a fresh token from client
290
+ 2. Wrong Firebase project - ensure service account matches your app
291
+ 3. Check Firebase Console for service status
409
292
 
410
- **Solution:**
293
+ ### "Emails not sending"
411
294
 
412
- 1. Ensure token hasn't expired (1 hour TTL) - client should obtain fresh token
413
- 2. Verify client and server use the same Firebase project
414
- 3. Confirm service account JSON matches your Firebase project ID
415
- 4. Check Firebase Console for service status
416
-
417
- ---
418
-
419
- ### 🔴 Email Not Sending
420
-
421
- **Solution:**
422
-
423
- Install and configure Strapi Email Plugin:
295
+ Install and configure Strapi email provider:
424
296
 
425
297
  ```bash
426
298
  yarn add @strapi/provider-email-sendgrid
@@ -428,128 +300,24 @@ yarn add @strapi/provider-email-sendgrid
428
300
 
429
301
  ```javascript
430
302
  // config/plugins.js
431
- email: {
432
- config: {
433
- provider: 'sendgrid',
434
- providerOptions: { apiKey: env('SENDGRID_API_KEY') },
435
- settings: {
436
- defaultFrom: 'noreply@yourapp.com'
437
- }
438
- }
439
- }
303
+ module.exports = () => ({
304
+ email: {
305
+ config: {
306
+ provider: "sendgrid",
307
+ providerOptions: { apiKey: process.env.SENDGRID_API_KEY },
308
+ settings: { defaultFrom: "noreply@yourapp.com" },
309
+ },
310
+ },
311
+ // ... firebase-authentication config
312
+ });
440
313
  ```
441
314
 
442
- Alternative: Install [Firebase Email Extension](https://extensions.dev/extensions/firebase/firestore-send-email)
443
-
444
- ---
445
-
446
- **Need more help?** Check [Firebase Console](https://console.firebase.google.com/) logs or [GitHub Issues](https://github.com/meta-cto/strapi-plugin-firebase-auth/issues)
447
-
448
- ## Best Practices
449
-
450
- - Use Firebase SDK for authentication (not `emailLogin` for production)
451
- - Store JWTs in httpOnly cookies (production) or secure storage (mobile)
452
- - Configure Strapi Email Plugin (SendGrid, Mailgun, SES) for production
453
- - Implement rate limiting on public endpoints
454
- - Enforce HTTPS for password reset URLs
455
- - Monitor Firebase quotas regularly
456
- - Keep dependencies updated
457
-
458
- ---
459
-
460
- ## Support
461
-
462
- ### Questions and Issues
463
-
464
- If you encounter problems or have questions:
465
-
466
- 1. **Check Troubleshooting Section:** Review common errors above
467
- 2. **Firebase Documentation:** [firebase.google.com/docs/auth](https://firebase.google.com/docs/auth)
468
- 3. **Strapi Documentation:** [docs.strapi.io](https://docs.strapi.io)
469
- 4. **GitHub Issues:** [github.com/meta-cto/strapi-plugin-firebase-auth/issues](https://github.com/meta-cto/strapi-plugin-firebase-auth/issues)
470
- - Search existing issues first
471
- - Provide detailed information when creating new issues
315
+ ## Links
472
316
 
473
- ### Creating a Bug Report
474
-
475
- When reporting issues, please include:
476
-
477
- 1. **Plugin version:** Check `package.json`
478
- 2. **Strapi version:** Run `yarn strapi version`
479
- 3. **Node version:** Run `node --version`
480
- 4. **Error message:** Full error text and stack trace
481
- 5. **Steps to reproduce:** Detailed steps to trigger the issue
482
- 6. **Configuration:** Relevant plugin configuration (redact sensitive data)
483
- 7. **Expected behavior:** What should happen
484
- 8. **Actual behavior:** What actually happens
485
-
486
- ### Feature Requests
487
-
488
- To request new features:
489
-
490
- 1. Search existing feature requests
491
- 2. Create detailed proposal with use case
492
- 3. Explain why feature would be beneficial
493
- 4. Suggest implementation approach (if applicable)
494
-
495
- ### Community
496
-
497
- - **GitHub Discussions:** Ask questions and share experiences
498
- - **Discord:** Join Strapi community Discord server
499
- - **Stack Overflow:** Tag questions with `strapi` and `firebase`
500
-
501
- ---
317
+ - [GitHub Issues](https://github.com/meta-cto/strapi-plugin-firebase-auth/issues)
318
+ - [Firebase Auth Docs](https://firebase.google.com/docs/auth)
319
+ - [Strapi v5 Docs](https://docs.strapi.io)
502
320
 
503
321
  ## License
504
322
 
505
- This plugin is licensed under the MIT License. See `LICENSE.md` for full details.
506
-
507
- ---
508
-
509
- ## Changelog
510
-
511
- See `CHANGELOG.md` for version history and release notes.
512
-
513
- ---
514
-
515
- ## Credits
516
-
517
- Developed and maintained by **Meta CTO** team.
518
-
519
- **Contributors:**
520
-
521
- - Firebase Admin SDK: Google
522
- - Strapi Framework: Strapi Solutions SAS
523
- - AES Encryption: crypto-js library
524
-
525
- ---
526
-
527
- ## Additional Resources
528
-
529
- **Firebase Documentation:**
530
-
531
- - [Firebase Authentication](https://firebase.google.com/docs/auth)
532
- - [Firebase Admin SDK](https://firebase.google.com/docs/admin/setup)
533
- - Platform Guides: [Web](https://firebase.google.com/docs/web/setup) | [iOS](https://firebase.google.com/docs/ios/setup) | [Android](https://firebase.google.com/docs/android/setup)
534
-
535
- **Strapi Documentation:**
536
-
537
- - [Strapi v5](https://docs.strapi.io/dev-docs/intro)
538
- - [Email Providers](https://market.strapi.io/providers) (SendGrid, Mailgun, Amazon SES)
539
-
540
- **Firebase Extensions:**
541
-
542
- - [Trigger Email Extension](https://extensions.dev/extensions/firebase/firestore-send-email)
543
-
544
- ---
545
-
546
- **Thank you for using Strapi Plugin Firebase Authentication!** 🎉
547
-
548
- If you find this plugin helpful, please consider:
549
-
550
- - Starring the GitHub repository
551
- - Sharing with your community
552
- - Contributing improvements
553
- - Reporting issues to help us improve
554
-
555
- Happy coding! 🚀
323
+ MIT License - see [LICENSE.md](LICENSE.md)
@@ -5,9 +5,9 @@ const admin = require("@strapi/strapi/admin");
5
5
  const reactRouterDom = require("react-router-dom");
6
6
  const rt = require("@radix-ui/react-tooltip");
7
7
  const m = require("react");
8
- const api = require("./api-B6slsjut.js");
8
+ const api = require("./api-C0GYp4ji.js");
9
9
  const reactIntl = require("react-intl");
10
- const index = require("./index-CD2ZZc6l.js");
10
+ const index = require("./index-BEmqy-R3.js");
11
11
  const styled = require("styled-components");
12
12
  const rx = require("react-icons/rx");
13
13
  const ai = require("react-icons/ai");
@@ -3,9 +3,9 @@ import { Layouts, useQueryParams as useQueryParams$1, getFetchClient, Pagination
3
3
  import { useNavigate, useLocation, useParams, Routes, Route } from "react-router-dom";
4
4
  import { Provider } from "@radix-ui/react-tooltip";
5
5
  import m__default, { useState, useCallback, useMemo, useEffect, useRef, useLayoutEffect } from "react";
6
- import { W as Wc, T, R, z as zn, A as Ar, k as k1, a as A1, b as R1, c as Tr, I, n as n1, C as C1, $ as $1, d as T1, y as yo, w as wo, e as d0, f as c0, o as ot, _ as _m, S as S1, X as X0, g as Cm, h as getFirebaseConfig$1, H as Hm, E as E1, P as Ps, i as o1, s as s1, j as a1, l as l1, m as bl } from "./api-BlWOjGIx.mjs";
6
+ import { W as Wc, T, R, z as zn, A as Ar, k as k1, a as A1, b as R1, c as Tr, I, n as n1, C as C1, $ as $1, d as T1, y as yo, w as wo, e as d0, f as c0, o as ot, _ as _m, S as S1, X as X0, g as Cm, h as getFirebaseConfig$1, H as Hm, E as E1, P as Ps, i as o1, s as s1, j as a1, l as l1, m as bl } from "./api-BXpBvqeN.mjs";
7
7
  import { useIntl } from "react-intl";
8
- import { i as isArguments_1, a as isBufferExports, b as isTypedArray_1, c as isLength_1, d as isFunction_1, _ as _Stack, e as _getTag, f as _equalArrays, g as _equalByTag, h as isObjectLike_1, j as getDefaultExportFromCjs, U as U2, k as _baseGetTag, l as _MapCache, m as _Symbol, n as m3, o as bn, N as Nn, Y as Y2, J as J2, P as PLUGIN_ID, p as getAugmentedNamespace, q as commonjsGlobal, u as un, s as sn, r as c5, w as w5, t as _3 } from "./index-BFe-pIBw.mjs";
8
+ import { i as isArguments_1, a as isBufferExports, b as isTypedArray_1, c as isLength_1, d as isFunction_1, _ as _Stack, e as _getTag, f as _equalArrays, g as _equalByTag, h as isObjectLike_1, j as getDefaultExportFromCjs, U as U2, k as _baseGetTag, l as _MapCache, m as _Symbol, n as m3, o as bn, N as Nn, Y as Y2, J as J2, P as PLUGIN_ID, p as getAugmentedNamespace, q as commonjsGlobal, u as un, s as sn, r as c5, w as w5, t as _3 } from "./index-K_RiV2x9.mjs";
9
9
  import styled from "styled-components";
10
10
  import { RxCheck, RxCross2 } from "react-icons/rx";
11
11
  import { AiOutlineUserAdd, AiFillPhone, AiFillMail, AiFillYahoo, AiFillGithub, AiFillTwitterCircle, AiFillFacebook, AiFillApple, AiFillGoogleCircle } from "react-icons/ai";
@@ -5,7 +5,7 @@ import { jsxs, Fragment as Fragment$1, jsx } from "react/jsx-runtime";
5
5
  import * as rt from "@radix-ui/react-tooltip";
6
6
  import * as we$1 from "react-dom";
7
7
  import we__default, { flushSync, createPortal } from "react-dom";
8
- import { q as commonjsGlobal, J as J2, R as R3, C as C5, v as f5, x as h5, w as w5, y as a5, N as Nn$1, z as r3, A as f3, u as un$1, K as K2, B as o5, P as PLUGIN_ID } from "./index-BFe-pIBw.mjs";
8
+ import { q as commonjsGlobal, J as J2, R as R3, C as C5, v as f5, x as h5, w as w5, y as a5, N as Nn$1, z as r3, A as f3, u as un$1, K as K2, B as o5, P as PLUGIN_ID } from "./index-K_RiV2x9.mjs";
9
9
  import { getFetchClient } from "@strapi/strapi/admin";
10
10
  function $b1b2314f5f9a1d84$export$25bec8c6f54ee79a(callback) {
11
11
  const callbackRef = useRef(callback);
@@ -4,7 +4,7 @@ const m = require("react");
4
4
  const jsxRuntime = require("react/jsx-runtime");
5
5
  const rt = require("@radix-ui/react-tooltip");
6
6
  const we$1 = require("react-dom");
7
- const index$2 = require("./index-CD2ZZc6l.js");
7
+ const index$2 = require("./index-BEmqy-R3.js");
8
8
  const admin = require("@strapi/strapi/admin");
9
9
  function _interopNamespace(e) {
10
10
  if (e && e.__esModule) return e;
@@ -2661,7 +2661,7 @@ const index = {
2661
2661
  id: `${PLUGIN_ID}.page.title`,
2662
2662
  defaultMessage: PLUGIN_ID
2663
2663
  },
2664
- Component: () => Promise.resolve().then(() => require("./App-DjeXCYLI.js")).then((mod) => ({
2664
+ Component: () => Promise.resolve().then(() => require("./App-CSFciBSN.js")).then((mod) => ({
2665
2665
  default: mod.App
2666
2666
  })),
2667
2667
  permissions: PERMISSIONS["menu-link"]
@@ -2683,7 +2683,7 @@ const index = {
2683
2683
  id: "settings",
2684
2684
  to: `/settings/${PLUGIN_ID}`,
2685
2685
  async Component() {
2686
- const component = await Promise.resolve().then(() => require("./index-DLFklFzP.js"));
2686
+ const component = await Promise.resolve().then(() => require("./index-CfYs8WkU.js"));
2687
2687
  return component.default;
2688
2688
  },
2689
2689
  permissions: PERMISSIONS["menu-link"]
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { useState, useEffect } from "react";
3
- import { T, R, I, Z as Zm, z as zn, X as X0, P as Ps, O as O1, E as E1, r as r1, n as n1, h as getFirebaseConfig, p as saveFirebaseConfig, q as delFirebaseConfig, t as savePasswordSettings } from "./api-BlWOjGIx.mjs";
3
+ import { T, R, I, Z as Zm, z as zn, X as X0, P as Ps, O as O1, E as E1, r as r1, n as n1, h as getFirebaseConfig, p as saveFirebaseConfig, q as delFirebaseConfig, t as savePasswordSettings } from "./api-BXpBvqeN.mjs";
4
4
  import { useNotification, Page } from "@strapi/strapi/admin";
5
5
  import { useNavigate } from "react-router-dom";
6
6
  function SettingsPage() {
@@ -356,7 +356,7 @@ function SettingsPage() {
356
356
  return /* @__PURE__ */ jsxs(Fragment, { children: [
357
357
  /* @__PURE__ */ jsx(T, { style: { padding: 32 }, direction: "column", alignItems: "flex-start", gap: 4, children: /* @__PURE__ */ jsxs(R, { style: { width: "100%" }, children: [
358
358
  /* @__PURE__ */ jsxs(R, { marginBottom: 6, padding: 4, background: "neutral0", borderRadius: "4px", shadow: "filterShadow", children: [
359
- /* @__PURE__ */ jsx(I, { variant: "alpha", as: "h2", style: { display: "block", marginBottom: "8px" }, children: "Firebase Authentication" }),
359
+ /* @__PURE__ */ jsx(I, { variant: "alpha", as: "h2", style: { display: "block", marginBottom: "8px", fontSize: "2.4rem", fontWeight: 600 }, children: "Firebase Authentication" }),
360
360
  /* @__PURE__ */ jsx(
361
361
  I,
362
362
  {
@@ -564,39 +564,45 @@ function SettingsPage() {
564
564
  ] })
565
565
  ] }) : /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(R, { padding: 4, background: "neutral0", children: [
566
566
  /* @__PURE__ */ jsxs(R, { marginBottom: 4, children: [
567
- /* @__PURE__ */ jsx(I, { variant: "delta", fontWeight: "bold", style: { marginBottom: "8px" }, children: "Service Account Configuration" }),
567
+ /* @__PURE__ */ jsx(I, { variant: "beta", fontWeight: "bold", style: { marginBottom: "8px" }, children: "Service Account Configuration" }),
568
568
  /* @__PURE__ */ jsx(R, { marginBottom: 3, children: /* @__PURE__ */ jsxs(I, { variant: "pi", textColor: "neutral600", component: "span", children: [
569
569
  /* @__PURE__ */ jsx("strong", { children: "Required" }),
570
570
  " - Enables Firebase Admin SDK for server-side authentication"
571
571
  ] }) }),
572
572
  /* @__PURE__ */ jsxs(T, { gap: 2, alignItems: "center", justifyContent: "space-between", children: [
573
573
  /* @__PURE__ */ jsxs(T, { gap: 2, alignItems: "center", children: [
574
- /* @__PURE__ */ jsxs(I, { variant: "omega", textColor: "neutral600", children: [
575
- "Project:",
576
- " ",
577
- firebaseJsonValue?.firebaseConfigJson && (() => {
578
- try {
579
- const config = JSON.parse(firebaseJsonValue.firebaseConfigJson);
580
- return config.project_id || config.projectId || "Unknown Project";
581
- } catch (e) {
582
- return "Invalid Config";
583
- }
584
- })()
585
- ] }),
574
+ firebaseJsonValue?.firebaseConfigJson && (() => {
575
+ try {
576
+ const config = JSON.parse(firebaseJsonValue.firebaseConfigJson);
577
+ const projectId = config.project_id || config.projectId || "Unknown Project";
578
+ const keyId = config.private_key_id || "N/A";
579
+ return /* @__PURE__ */ jsxs(I, { variant: "omega", textColor: "neutral600", children: [
580
+ "Project: ",
581
+ projectId,
582
+ " | Key: ",
583
+ keyId
584
+ ] });
585
+ } catch (e) {
586
+ return /* @__PURE__ */ jsx(I, { variant: "omega", textColor: "neutral600", children: "Invalid Config" });
587
+ }
588
+ })(),
586
589
  /* @__PURE__ */ jsx(Ps, { backgroundColor: "success200", textColor: "success700", size: "S", children: "✓ CONFIGURED" })
587
590
  ] }),
588
591
  /* @__PURE__ */ jsx(zn, { variant: "danger-light", size: "S", onClick: handleDeleteFirebaseJsonConfig, children: "Delete Config" })
589
592
  ] })
590
593
  ] }),
591
594
  /* @__PURE__ */ jsxs(R, { paddingTop: 4, style: { borderTop: "1px solid #eaeaef" }, children: [
592
- /* @__PURE__ */ jsx(I, { variant: "delta", fontWeight: "bold", style: { marginBottom: "8px" }, children: "Web API Key Configuration" }),
595
+ /* @__PURE__ */ jsx(I, { variant: "beta", fontWeight: "bold", style: { marginBottom: "8px" }, children: "Web API Key Configuration" }),
593
596
  /* @__PURE__ */ jsx(R, { marginBottom: 3, children: /* @__PURE__ */ jsxs(I, { variant: "pi", textColor: "neutral600", component: "span", children: [
594
597
  /* @__PURE__ */ jsx("strong", { children: "Optional" }),
595
598
  " - Only needed for email/password login via emailLogin endpoint"
596
599
  ] }) }),
597
600
  /* @__PURE__ */ jsxs(T, { gap: 2, alignItems: "center", justifyContent: "space-between", children: [
598
601
  /* @__PURE__ */ jsxs(T, { gap: 2, alignItems: "center", children: [
599
- firebaseWebApiKey?.trim() && /* @__PURE__ */ jsx(I, { variant: "omega", textColor: "neutral600", children: `${firebaseWebApiKey.substring(0, 10)}...` }),
602
+ firebaseWebApiKey?.trim() && /* @__PURE__ */ jsxs(I, { variant: "omega", textColor: "neutral600", children: [
603
+ "Key: ",
604
+ firebaseWebApiKey
605
+ ] }),
600
606
  firebaseWebApiKey?.trim() ? /* @__PURE__ */ jsx(Ps, { backgroundColor: "success200", textColor: "success700", size: "S", children: "✓ CONFIGURED" }) : /* @__PURE__ */ jsx(Ps, { backgroundColor: "neutral200", textColor: "neutral700", size: "S", children: "NOT SET" })
601
607
  ] }),
602
608
  firebaseWebApiKey?.trim() ? /* @__PURE__ */ jsx(zn, { variant: "danger-light", size: "S", onClick: handleRemoveWebApiKey, children: "Delete Config" }) : /* @__PURE__ */ jsx(zn, { variant: "secondary", size: "S", onClick: handleAddWebApiKey, children: "+ Add Web API Key" })
@@ -606,7 +612,7 @@ function SettingsPage() {
606
612
  })()
607
613
  ] }),
608
614
  /* @__PURE__ */ jsxs(R, { padding: 4, background: "neutral0", borderRadius: "4px", shadow: "filterShadow", marginBottom: 6, children: [
609
- /* @__PURE__ */ jsx(I, { variant: "alpha", as: "h2", style: { display: "block", marginBottom: "8px" }, children: "Password Reset Settings" }),
615
+ /* @__PURE__ */ jsx(I, { variant: "alpha", as: "h2", style: { display: "block", marginBottom: "8px", fontSize: "2.4rem", fontWeight: 600 }, children: "Password Reset Settings" }),
610
616
  /* @__PURE__ */ jsx(
611
617
  I,
612
618
  {
@@ -729,7 +735,7 @@ function SettingsPage() {
729
735
  )
730
736
  ] }),
731
737
  /* @__PURE__ */ jsxs(R, { padding: 4, background: "neutral0", borderRadius: "4px", shadow: "filterShadow", marginBottom: 6, children: [
732
- /* @__PURE__ */ jsx(I, { variant: "alpha", as: "h2", style: { display: "block", marginBottom: "8px" }, children: "Email Verification" }),
738
+ /* @__PURE__ */ jsx(I, { variant: "alpha", as: "h2", style: { display: "block", marginBottom: "8px", fontSize: "2.4rem", fontWeight: 600 }, children: "Email Verification" }),
733
739
  /* @__PURE__ */ jsx(
734
740
  I,
735
741
  {
@@ -802,7 +808,7 @@ function SettingsPage() {
802
808
  )
803
809
  ] }),
804
810
  /* @__PURE__ */ jsxs(R, { padding: 4, background: "neutral0", borderRadius: "4px", shadow: "filterShadow", marginBottom: 6, children: [
805
- /* @__PURE__ */ jsx(I, { variant: "alpha", as: "h2", style: { display: "block", marginBottom: "8px" }, children: "Magic Link Authentication" }),
811
+ /* @__PURE__ */ jsx(I, { variant: "alpha", as: "h2", style: { display: "block", marginBottom: "8px", fontSize: "2.4rem", fontWeight: 600 }, children: "Magic Link Authentication" }),
806
812
  /* @__PURE__ */ jsx(
807
813
  I,
808
814
  {
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const m = require("react");
5
- const api = require("./api-B6slsjut.js");
5
+ const api = require("./api-C0GYp4ji.js");
6
6
  const admin = require("@strapi/strapi/admin");
7
7
  const reactRouterDom = require("react-router-dom");
8
8
  function SettingsPage() {
@@ -358,7 +358,7 @@ function SettingsPage() {
358
358
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
359
359
  /* @__PURE__ */ jsxRuntime.jsx(api.T, { style: { padding: 32 }, direction: "column", alignItems: "flex-start", gap: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(api.R, { style: { width: "100%" }, children: [
360
360
  /* @__PURE__ */ jsxRuntime.jsxs(api.R, { marginBottom: 6, padding: 4, background: "neutral0", borderRadius: "4px", shadow: "filterShadow", children: [
361
- /* @__PURE__ */ jsxRuntime.jsx(api.I, { variant: "alpha", as: "h2", style: { display: "block", marginBottom: "8px" }, children: "Firebase Authentication" }),
361
+ /* @__PURE__ */ jsxRuntime.jsx(api.I, { variant: "alpha", as: "h2", style: { display: "block", marginBottom: "8px", fontSize: "2.4rem", fontWeight: 600 }, children: "Firebase Authentication" }),
362
362
  /* @__PURE__ */ jsxRuntime.jsx(
363
363
  api.I,
364
364
  {
@@ -566,39 +566,45 @@ function SettingsPage() {
566
566
  ] })
567
567
  ] }) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsxs(api.R, { padding: 4, background: "neutral0", children: [
568
568
  /* @__PURE__ */ jsxRuntime.jsxs(api.R, { marginBottom: 4, children: [
569
- /* @__PURE__ */ jsxRuntime.jsx(api.I, { variant: "delta", fontWeight: "bold", style: { marginBottom: "8px" }, children: "Service Account Configuration" }),
569
+ /* @__PURE__ */ jsxRuntime.jsx(api.I, { variant: "beta", fontWeight: "bold", style: { marginBottom: "8px" }, children: "Service Account Configuration" }),
570
570
  /* @__PURE__ */ jsxRuntime.jsx(api.R, { marginBottom: 3, children: /* @__PURE__ */ jsxRuntime.jsxs(api.I, { variant: "pi", textColor: "neutral600", component: "span", children: [
571
571
  /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Required" }),
572
572
  " - Enables Firebase Admin SDK for server-side authentication"
573
573
  ] }) }),
574
574
  /* @__PURE__ */ jsxRuntime.jsxs(api.T, { gap: 2, alignItems: "center", justifyContent: "space-between", children: [
575
575
  /* @__PURE__ */ jsxRuntime.jsxs(api.T, { gap: 2, alignItems: "center", children: [
576
- /* @__PURE__ */ jsxRuntime.jsxs(api.I, { variant: "omega", textColor: "neutral600", children: [
577
- "Project:",
578
- " ",
579
- firebaseJsonValue?.firebaseConfigJson && (() => {
580
- try {
581
- const config = JSON.parse(firebaseJsonValue.firebaseConfigJson);
582
- return config.project_id || config.projectId || "Unknown Project";
583
- } catch (e) {
584
- return "Invalid Config";
585
- }
586
- })()
587
- ] }),
576
+ firebaseJsonValue?.firebaseConfigJson && (() => {
577
+ try {
578
+ const config = JSON.parse(firebaseJsonValue.firebaseConfigJson);
579
+ const projectId = config.project_id || config.projectId || "Unknown Project";
580
+ const keyId = config.private_key_id || "N/A";
581
+ return /* @__PURE__ */ jsxRuntime.jsxs(api.I, { variant: "omega", textColor: "neutral600", children: [
582
+ "Project: ",
583
+ projectId,
584
+ " | Key: ",
585
+ keyId
586
+ ] });
587
+ } catch (e) {
588
+ return /* @__PURE__ */ jsxRuntime.jsx(api.I, { variant: "omega", textColor: "neutral600", children: "Invalid Config" });
589
+ }
590
+ })(),
588
591
  /* @__PURE__ */ jsxRuntime.jsx(api.Ps, { backgroundColor: "success200", textColor: "success700", size: "S", children: "✓ CONFIGURED" })
589
592
  ] }),
590
593
  /* @__PURE__ */ jsxRuntime.jsx(api.zn, { variant: "danger-light", size: "S", onClick: handleDeleteFirebaseJsonConfig, children: "Delete Config" })
591
594
  ] })
592
595
  ] }),
593
596
  /* @__PURE__ */ jsxRuntime.jsxs(api.R, { paddingTop: 4, style: { borderTop: "1px solid #eaeaef" }, children: [
594
- /* @__PURE__ */ jsxRuntime.jsx(api.I, { variant: "delta", fontWeight: "bold", style: { marginBottom: "8px" }, children: "Web API Key Configuration" }),
597
+ /* @__PURE__ */ jsxRuntime.jsx(api.I, { variant: "beta", fontWeight: "bold", style: { marginBottom: "8px" }, children: "Web API Key Configuration" }),
595
598
  /* @__PURE__ */ jsxRuntime.jsx(api.R, { marginBottom: 3, children: /* @__PURE__ */ jsxRuntime.jsxs(api.I, { variant: "pi", textColor: "neutral600", component: "span", children: [
596
599
  /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Optional" }),
597
600
  " - Only needed for email/password login via emailLogin endpoint"
598
601
  ] }) }),
599
602
  /* @__PURE__ */ jsxRuntime.jsxs(api.T, { gap: 2, alignItems: "center", justifyContent: "space-between", children: [
600
603
  /* @__PURE__ */ jsxRuntime.jsxs(api.T, { gap: 2, alignItems: "center", children: [
601
- firebaseWebApiKey?.trim() && /* @__PURE__ */ jsxRuntime.jsx(api.I, { variant: "omega", textColor: "neutral600", children: `${firebaseWebApiKey.substring(0, 10)}...` }),
604
+ firebaseWebApiKey?.trim() && /* @__PURE__ */ jsxRuntime.jsxs(api.I, { variant: "omega", textColor: "neutral600", children: [
605
+ "Key: ",
606
+ firebaseWebApiKey
607
+ ] }),
602
608
  firebaseWebApiKey?.trim() ? /* @__PURE__ */ jsxRuntime.jsx(api.Ps, { backgroundColor: "success200", textColor: "success700", size: "S", children: "✓ CONFIGURED" }) : /* @__PURE__ */ jsxRuntime.jsx(api.Ps, { backgroundColor: "neutral200", textColor: "neutral700", size: "S", children: "NOT SET" })
603
609
  ] }),
604
610
  firebaseWebApiKey?.trim() ? /* @__PURE__ */ jsxRuntime.jsx(api.zn, { variant: "danger-light", size: "S", onClick: handleRemoveWebApiKey, children: "Delete Config" }) : /* @__PURE__ */ jsxRuntime.jsx(api.zn, { variant: "secondary", size: "S", onClick: handleAddWebApiKey, children: "+ Add Web API Key" })
@@ -608,7 +614,7 @@ function SettingsPage() {
608
614
  })()
609
615
  ] }),
610
616
  /* @__PURE__ */ jsxRuntime.jsxs(api.R, { padding: 4, background: "neutral0", borderRadius: "4px", shadow: "filterShadow", marginBottom: 6, children: [
611
- /* @__PURE__ */ jsxRuntime.jsx(api.I, { variant: "alpha", as: "h2", style: { display: "block", marginBottom: "8px" }, children: "Password Reset Settings" }),
617
+ /* @__PURE__ */ jsxRuntime.jsx(api.I, { variant: "alpha", as: "h2", style: { display: "block", marginBottom: "8px", fontSize: "2.4rem", fontWeight: 600 }, children: "Password Reset Settings" }),
612
618
  /* @__PURE__ */ jsxRuntime.jsx(
613
619
  api.I,
614
620
  {
@@ -731,7 +737,7 @@ function SettingsPage() {
731
737
  )
732
738
  ] }),
733
739
  /* @__PURE__ */ jsxRuntime.jsxs(api.R, { padding: 4, background: "neutral0", borderRadius: "4px", shadow: "filterShadow", marginBottom: 6, children: [
734
- /* @__PURE__ */ jsxRuntime.jsx(api.I, { variant: "alpha", as: "h2", style: { display: "block", marginBottom: "8px" }, children: "Email Verification" }),
740
+ /* @__PURE__ */ jsxRuntime.jsx(api.I, { variant: "alpha", as: "h2", style: { display: "block", marginBottom: "8px", fontSize: "2.4rem", fontWeight: 600 }, children: "Email Verification" }),
735
741
  /* @__PURE__ */ jsxRuntime.jsx(
736
742
  api.I,
737
743
  {
@@ -804,7 +810,7 @@ function SettingsPage() {
804
810
  )
805
811
  ] }),
806
812
  /* @__PURE__ */ jsxRuntime.jsxs(api.R, { padding: 4, background: "neutral0", borderRadius: "4px", shadow: "filterShadow", marginBottom: 6, children: [
807
- /* @__PURE__ */ jsxRuntime.jsx(api.I, { variant: "alpha", as: "h2", style: { display: "block", marginBottom: "8px" }, children: "Magic Link Authentication" }),
813
+ /* @__PURE__ */ jsxRuntime.jsx(api.I, { variant: "alpha", as: "h2", style: { display: "block", marginBottom: "8px", fontSize: "2.4rem", fontWeight: 600 }, children: "Magic Link Authentication" }),
808
814
  /* @__PURE__ */ jsxRuntime.jsx(
809
815
  api.I,
810
816
  {
@@ -2660,7 +2660,7 @@ const index = {
2660
2660
  id: `${PLUGIN_ID}.page.title`,
2661
2661
  defaultMessage: PLUGIN_ID
2662
2662
  },
2663
- Component: () => import("./App-DWb7aV0a.mjs").then((mod) => ({
2663
+ Component: () => import("./App-Dzl5W6YR.mjs").then((mod) => ({
2664
2664
  default: mod.App
2665
2665
  })),
2666
2666
  permissions: PERMISSIONS["menu-link"]
@@ -2682,7 +2682,7 @@ const index = {
2682
2682
  id: "settings",
2683
2683
  to: `/settings/${PLUGIN_ID}`,
2684
2684
  async Component() {
2685
- const component = await import("./index-jxnRtalg.mjs");
2685
+ const component = await import("./index-CCa5Fl4r.mjs");
2686
2686
  return component.default;
2687
2687
  },
2688
2688
  permissions: PERMISSIONS["menu-link"]
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const index = require("../_chunks/index-CD2ZZc6l.js");
2
+ const index = require("../_chunks/index-BEmqy-R3.js");
3
3
  require("react/jsx-runtime");
4
4
  require("@strapi/strapi/admin");
5
5
  require("react-router-dom");
@@ -1,4 +1,4 @@
1
- import { D } from "../_chunks/index-BFe-pIBw.mjs";
1
+ import { D } from "../_chunks/index-K_RiV2x9.mjs";
2
2
  import "react/jsx-runtime";
3
3
  import "@strapi/strapi/admin";
4
4
  import "react-router-dom";
@@ -393,9 +393,6 @@ const bootstrap = async ({ strapi: strapi2 }) => {
393
393
  } catch (error) {
394
394
  strapi2.log.warn(`[Firebase Auth] Migration warning: ${error.message}`);
395
395
  }
396
- if (process.env.FIREBASE_REPORT_ORPHANS === "true") {
397
- await reportOrphanUsers(strapi2);
398
- }
399
396
  if (process.env.RUN_FIREBASE_MIGRATION === "true") {
400
397
  const dryRun = process.env.DRY_RUN === "true";
401
398
  strapi2.log.info("");
@@ -407,6 +404,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
407
404
  try {
408
405
  strapi2.log.info("[Firebase Auth] Auto-link enabled via FIREBASE_AUTO_LINK_ON_STARTUP");
409
406
  await strapi2.plugin("firebase-authentication").service("autoLinkService").linkAllUsers(strapi2);
407
+ await reportOrphanUsers(strapi2);
410
408
  } catch (error) {
411
409
  strapi2.log.error(`Auto-linking failed: ${error.message}`);
412
410
  }
@@ -366,9 +366,6 @@ const bootstrap = async ({ strapi: strapi2 }) => {
366
366
  } catch (error) {
367
367
  strapi2.log.warn(`[Firebase Auth] Migration warning: ${error.message}`);
368
368
  }
369
- if (process.env.FIREBASE_REPORT_ORPHANS === "true") {
370
- await reportOrphanUsers(strapi2);
371
- }
372
369
  if (process.env.RUN_FIREBASE_MIGRATION === "true") {
373
370
  const dryRun = process.env.DRY_RUN === "true";
374
371
  strapi2.log.info("");
@@ -380,12 +377,15 @@ const bootstrap = async ({ strapi: strapi2 }) => {
380
377
  try {
381
378
  strapi2.log.info("[Firebase Auth] Auto-link enabled via FIREBASE_AUTO_LINK_ON_STARTUP");
382
379
  await strapi2.plugin("firebase-authentication").service("autoLinkService").linkAllUsers(strapi2);
380
+ await reportOrphanUsers(strapi2);
383
381
  } catch (error) {
384
382
  strapi2.log.error(`Auto-linking failed: ${error.message}`);
385
383
  }
386
384
  });
387
385
  } else {
388
- strapi2.log.debug("[Firebase Auth] Startup auto-link disabled (set FIREBASE_AUTO_LINK_ON_STARTUP=true to enable)");
386
+ strapi2.log.debug(
387
+ "[Firebase Auth] Startup auto-link disabled (set FIREBASE_AUTO_LINK_ON_STARTUP=true to enable)"
388
+ );
389
389
  }
390
390
  const pluginConfig = strapi2.config.get("plugin::firebase-authentication");
391
391
  const retentionDays = pluginConfig?.activityLogRetentionDays;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-firebase-authentication",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Allows easy integration between clients utilizing Firebase for authentication and Strapi",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -11,7 +11,10 @@
11
11
  "url": "https://github.com/Meta-CTO/strapi-plugin-firebase-auth/issues"
12
12
  },
13
13
  "homepage": "https://github.com/Meta-CTO/strapi-plugin-firebase-auth#readme",
14
- "author": "Garrett Fritz <garrett@metacto.com>",
14
+ "authors": [
15
+ "Garrett Fritz <garrett@metacto.com>",
16
+ "Felippe Haeitmann <felippe.haeitmann@metacto.com>"
17
+ ],
15
18
  "keywords": [
16
19
  "strapi5",
17
20
  "strapi-plugin"