@xbg.solutions/create-frontend 1.1.1
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/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +4 -0
- package/lib/index.js.map +1 -0
- package/package.json +21 -0
- package/src/generate-component.cjs +602 -0
- package/src/generate-route.cjs +774 -0
- package/src/generate-service.cjs +1306 -0
- package/src/index.ts +2 -0
- package/src/manage-auth-users.cjs +410 -0
- package/src/setup.cjs +1049 -0
- package/src/validate-setup.cjs +341 -0
- package/tsconfig.json +20 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Firebase Auth User Management Script (DEVELOPMENT ONLY)
|
|
4
|
+
*
|
|
5
|
+
* WARNING: This script is intended for local development and testing only.
|
|
6
|
+
* It should be REMOVED from your project before deploying to production
|
|
7
|
+
* as it could create significant security vulnerabilities.
|
|
8
|
+
*
|
|
9
|
+
* Functions:
|
|
10
|
+
* - Create users with email-link or phone auth
|
|
11
|
+
* - Add, edit, or remove custom attributes from users
|
|
12
|
+
* - List existing users with their custom attributes
|
|
13
|
+
*
|
|
14
|
+
* Requirements:
|
|
15
|
+
* - Firebase Admin SDK - install as a dev dependency!
|
|
16
|
+
* - Node.js 14+
|
|
17
|
+
* - service-account.json file in the project root or set FIREBASE_SERVICE_ACCOUNT_PATH environment variable
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const readline = require('readline');
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const admin = require('firebase-admin');
|
|
24
|
+
const chalk = require('chalk');
|
|
25
|
+
|
|
26
|
+
// Check if .env file exists and load environment variables
|
|
27
|
+
const dotenvPath = path.resolve(__dirname, '../.env');
|
|
28
|
+
if (fs.existsSync(dotenvPath)) {
|
|
29
|
+
require('dotenv').config({ path: dotenvPath });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Security warning
|
|
33
|
+
console.log(chalk.bgRed.white.bold('⚠️ SECURITY WARNING ⚠️'));
|
|
34
|
+
console.log(chalk.yellow('\nThis script is for DEVELOPMENT USE ONLY.'));
|
|
35
|
+
console.log(chalk.yellow('It should be REMOVED before deploying to production.'));
|
|
36
|
+
console.log(chalk.yellow('It provides administrative access to Firebase Authentication.\n'));
|
|
37
|
+
|
|
38
|
+
// Create readline interface for user input
|
|
39
|
+
const rl = readline.createInterface({
|
|
40
|
+
input: process.stdin,
|
|
41
|
+
output: process.stdout
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Initialize Firebase Admin SDK
|
|
45
|
+
let firebaseInitialized = false;
|
|
46
|
+
|
|
47
|
+
function initializeFirebase() {
|
|
48
|
+
if (firebaseInitialized) return;
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// Try to initialize using environment variables
|
|
52
|
+
if (process.env.FIREBASE_SERVICE_ACCOUNT_JSON) {
|
|
53
|
+
const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT_JSON);
|
|
54
|
+
admin.initializeApp({
|
|
55
|
+
credential: admin.credential.cert(serviceAccount)
|
|
56
|
+
});
|
|
57
|
+
firebaseInitialized = true;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Try to initialize using service account file
|
|
62
|
+
const serviceAccountPath = process.env.FIREBASE_SERVICE_ACCOUNT_PATH ||
|
|
63
|
+
path.resolve(__dirname, '../service-account.json');
|
|
64
|
+
|
|
65
|
+
if (fs.existsSync(serviceAccountPath)) {
|
|
66
|
+
const serviceAccount = require(serviceAccountPath);
|
|
67
|
+
admin.initializeApp({
|
|
68
|
+
credential: admin.credential.cert(serviceAccount)
|
|
69
|
+
});
|
|
70
|
+
firebaseInitialized = true;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// If no service account found, prompt for manual input
|
|
75
|
+
console.log(chalk.red('No Firebase service account found.'));
|
|
76
|
+
console.log('You need to provide Firebase Admin SDK credentials to continue.');
|
|
77
|
+
console.log('Please follow these steps:');
|
|
78
|
+
console.log('1. Go to Firebase Console > Project Settings > Service Accounts');
|
|
79
|
+
console.log('2. Click "Generate New Private Key"');
|
|
80
|
+
console.log('3. Save the JSON file in your project root as "service-account.json"');
|
|
81
|
+
console.log(' or set FIREBASE_SERVICE_ACCOUNT_PATH environment variable\n');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error('Failed to initialize Firebase Admin SDK:', error);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Helper function to prompt user
|
|
90
|
+
async function prompt(question) {
|
|
91
|
+
return new Promise((resolve) => {
|
|
92
|
+
rl.question(question, (answer) => {
|
|
93
|
+
resolve(answer.trim());
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Helper function for menu selection
|
|
99
|
+
async function showMenu(title, options) {
|
|
100
|
+
console.log(chalk.cyan(`\n${title}`));
|
|
101
|
+
|
|
102
|
+
options.forEach((option, index) => {
|
|
103
|
+
console.log(`${index + 1}. ${option}`);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const selection = await prompt('\nSelect an option (number): ');
|
|
107
|
+
const numSelection = parseInt(selection, 10);
|
|
108
|
+
|
|
109
|
+
if (isNaN(numSelection) || numSelection < 1 || numSelection > options.length) {
|
|
110
|
+
console.log(chalk.red('Invalid selection. Please try again.'));
|
|
111
|
+
return showMenu(title, options);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return numSelection;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// List all users
|
|
118
|
+
async function listUsers() {
|
|
119
|
+
console.log(chalk.cyan('\nFetching users...'));
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const listUsersResult = await admin.auth().listUsers(1000);
|
|
123
|
+
|
|
124
|
+
if (listUsersResult.users.length === 0) {
|
|
125
|
+
console.log(chalk.yellow('No users found in your Firebase project.'));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log(chalk.green(`\nFound ${listUsersResult.users.length} users:`));
|
|
130
|
+
|
|
131
|
+
for (const user of listUsersResult.users) {
|
|
132
|
+
console.log(chalk.cyan('\n-----------------------------------------'));
|
|
133
|
+
console.log(`UID: ${chalk.yellow(user.uid)}`);
|
|
134
|
+
console.log(`Email: ${user.email || 'N/A'}`);
|
|
135
|
+
console.log(`Phone: ${user.phoneNumber || 'N/A'}`);
|
|
136
|
+
console.log(`Email Verified: ${user.emailVerified}`);
|
|
137
|
+
|
|
138
|
+
// Display custom claims (attributes)
|
|
139
|
+
const customClaims = user.customClaims || {};
|
|
140
|
+
console.log(chalk.cyan('\nCustom Attributes:'));
|
|
141
|
+
|
|
142
|
+
if (Object.keys(customClaims).length === 0) {
|
|
143
|
+
console.log(' No custom attributes');
|
|
144
|
+
} else {
|
|
145
|
+
for (const [key, value] of Object.entries(customClaims)) {
|
|
146
|
+
console.log(` ${key}: ${chalk.green(JSON.stringify(value))}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error(chalk.red('Error fetching users:'), error);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Create a new user
|
|
156
|
+
async function createUser() {
|
|
157
|
+
const authType = await showMenu('Select authentication method:', [
|
|
158
|
+
'Email Link',
|
|
159
|
+
'Phone Number'
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
let userRecord;
|
|
164
|
+
|
|
165
|
+
if (authType === 1) {
|
|
166
|
+
// Email Link user
|
|
167
|
+
const email = await prompt('Enter email address: ');
|
|
168
|
+
|
|
169
|
+
if (!email || !email.includes('@')) {
|
|
170
|
+
console.log(chalk.red('Invalid email address. Please try again.'));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const emailVerified = true; // Always true for development convenience
|
|
175
|
+
const password = Math.random().toString(36).slice(-10); // Random password
|
|
176
|
+
|
|
177
|
+
userRecord = await admin.auth().createUser({
|
|
178
|
+
email,
|
|
179
|
+
emailVerified,
|
|
180
|
+
password
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
console.log(chalk.green('\nEmail Link User created successfully:'));
|
|
184
|
+
console.log(`UID: ${chalk.yellow(userRecord.uid)}`);
|
|
185
|
+
console.log(`Email: ${userRecord.email}`);
|
|
186
|
+
console.log('Password: [auto-generated, not needed for email link auth]');
|
|
187
|
+
} else {
|
|
188
|
+
// Phone Number user
|
|
189
|
+
const phoneNumber = await prompt('Enter phone number (format: +11234567890): ');
|
|
190
|
+
|
|
191
|
+
if (!phoneNumber || !phoneNumber.startsWith('+')) {
|
|
192
|
+
console.log(chalk.red('Invalid phone number. Must start with + and country code.'));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
userRecord = await admin.auth().createUser({
|
|
197
|
+
phoneNumber
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
console.log(chalk.green('\nPhone Number User created successfully:'));
|
|
201
|
+
console.log(`UID: ${chalk.yellow(userRecord.uid)}`);
|
|
202
|
+
console.log(`Phone: ${userRecord.phoneNumber}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Ask if they want to add custom attributes now
|
|
206
|
+
const addAttributesNow = await prompt('Do you want to add custom attributes now? (y/n): ');
|
|
207
|
+
|
|
208
|
+
if (addAttributesNow.toLowerCase() === 'y') {
|
|
209
|
+
await manageCustomAttributes(userRecord.uid);
|
|
210
|
+
}
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error(chalk.red('Error creating user:'), error);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Manage custom attributes for a user
|
|
217
|
+
async function manageCustomAttributes(presetUid = null) {
|
|
218
|
+
let uid = presetUid;
|
|
219
|
+
|
|
220
|
+
if (!uid) {
|
|
221
|
+
uid = await prompt('Enter user UID: ');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
// Get the current user and their custom claims
|
|
226
|
+
const userRecord = await admin.auth().getUser(uid);
|
|
227
|
+
const currentClaims = userRecord.customClaims || {};
|
|
228
|
+
|
|
229
|
+
console.log(chalk.cyan(`\nManaging custom attributes for user: ${userRecord.email || userRecord.phoneNumber || uid}`));
|
|
230
|
+
console.log('\nCurrent custom attributes:');
|
|
231
|
+
|
|
232
|
+
if (Object.keys(currentClaims).length === 0) {
|
|
233
|
+
console.log(' No custom attributes');
|
|
234
|
+
} else {
|
|
235
|
+
for (const [key, value] of Object.entries(currentClaims)) {
|
|
236
|
+
console.log(` ${key}: ${chalk.green(JSON.stringify(value))}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const action = await showMenu('\nWhat would you like to do?', [
|
|
241
|
+
'Add or update attribute',
|
|
242
|
+
'Add common roles (client, consultant, admin, sysAdmin)',
|
|
243
|
+
'Remove attribute',
|
|
244
|
+
'Clear all attributes',
|
|
245
|
+
'Go back'
|
|
246
|
+
]);
|
|
247
|
+
|
|
248
|
+
if (action === 1) {
|
|
249
|
+
// Add or update attribute
|
|
250
|
+
const key = await prompt('Enter attribute name: ');
|
|
251
|
+
|
|
252
|
+
if (!key) {
|
|
253
|
+
console.log(chalk.red('Invalid attribute name.'));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
console.log(chalk.yellow('\nEnter attribute value (supports JSON).'));
|
|
258
|
+
console.log('For string: just type the value');
|
|
259
|
+
console.log('For boolean: type "true" or "false"');
|
|
260
|
+
console.log('For number: type the number');
|
|
261
|
+
console.log('For JSON: type an object like {"key": "value"}');
|
|
262
|
+
|
|
263
|
+
const valueStr = await prompt('Value: ');
|
|
264
|
+
let value;
|
|
265
|
+
|
|
266
|
+
// Try to parse as JSON, otherwise use as string/boolean/number
|
|
267
|
+
try {
|
|
268
|
+
if (valueStr.toLowerCase() === 'true') {
|
|
269
|
+
value = true;
|
|
270
|
+
} else if (valueStr.toLowerCase() === 'false') {
|
|
271
|
+
value = false;
|
|
272
|
+
} else if (!isNaN(Number(valueStr))) {
|
|
273
|
+
value = Number(valueStr);
|
|
274
|
+
} else if (valueStr.startsWith('{') || valueStr.startsWith('[')) {
|
|
275
|
+
value = JSON.parse(valueStr);
|
|
276
|
+
} else {
|
|
277
|
+
value = valueStr;
|
|
278
|
+
}
|
|
279
|
+
} catch (error) {
|
|
280
|
+
value = valueStr; // Use as string if JSON parsing fails
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Update claims
|
|
284
|
+
const newClaims = {
|
|
285
|
+
...currentClaims,
|
|
286
|
+
[key]: value
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
await admin.auth().setCustomUserClaims(uid, newClaims);
|
|
290
|
+
console.log(chalk.green(`\nCustom attribute '${key}' set successfully.`));
|
|
291
|
+
} else if (action === 2) {
|
|
292
|
+
// Add common roles
|
|
293
|
+
const roles = [];
|
|
294
|
+
|
|
295
|
+
const isClient = await prompt('Add client role? (y/n): ');
|
|
296
|
+
if (isClient.toLowerCase() === 'y') roles.push('isClient');
|
|
297
|
+
|
|
298
|
+
const isConsultant = await prompt('Add consultant role? (y/n): ');
|
|
299
|
+
if (isConsultant.toLowerCase() === 'y') roles.push('isConsultant');
|
|
300
|
+
|
|
301
|
+
const isAdmin = await prompt('Add admin role? (y/n): ');
|
|
302
|
+
if (isAdmin.toLowerCase() === 'y') roles.push('isAdmin');
|
|
303
|
+
|
|
304
|
+
const isSysAdmin = await prompt('Add sysAdmin role? (y/n): ');
|
|
305
|
+
if (isSysAdmin.toLowerCase() === 'y') roles.push('isSysAdmin');
|
|
306
|
+
|
|
307
|
+
// Check if user selected any roles
|
|
308
|
+
if (roles.length === 0) {
|
|
309
|
+
console.log(chalk.yellow('No roles selected.'));
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Update individual role flags
|
|
314
|
+
const newClaims = { ...currentClaims };
|
|
315
|
+
|
|
316
|
+
// Always set all role flags explicitly (true/false)
|
|
317
|
+
newClaims.isClient = roles.includes('isClient');
|
|
318
|
+
newClaims.isConsultant = roles.includes('isConsultant');
|
|
319
|
+
newClaims.isAdmin = roles.includes('isAdmin');
|
|
320
|
+
newClaims.isSysAdmin = roles.includes('isSysAdmin');
|
|
321
|
+
|
|
322
|
+
// Keep existing roles array or create a new one
|
|
323
|
+
newClaims.roles = roles;
|
|
324
|
+
|
|
325
|
+
await admin.auth().setCustomUserClaims(uid, newClaims);
|
|
326
|
+
console.log(chalk.green('\nCommon roles updated successfully.'));
|
|
327
|
+
} else if (action === 3) {
|
|
328
|
+
// Remove attribute
|
|
329
|
+
const key = await prompt('Enter attribute name to remove: ');
|
|
330
|
+
|
|
331
|
+
if (!key || !(key in currentClaims)) {
|
|
332
|
+
console.log(chalk.red(`Attribute '${key}' not found.`));
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Create a new claims object without the specified key
|
|
337
|
+
const { [key]: removedValue, ...newClaims } = currentClaims;
|
|
338
|
+
|
|
339
|
+
await admin.auth().setCustomUserClaims(uid, newClaims);
|
|
340
|
+
console.log(chalk.green(`\nCustom attribute '${key}' removed successfully.`));
|
|
341
|
+
} else if (action === 4) {
|
|
342
|
+
// Clear all attributes
|
|
343
|
+
const confirm = await prompt('Are you sure you want to clear all custom attributes? (y/n): ');
|
|
344
|
+
|
|
345
|
+
if (confirm.toLowerCase() === 'y') {
|
|
346
|
+
await admin.auth().setCustomUserClaims(uid, {});
|
|
347
|
+
console.log(chalk.green('\nAll custom attributes cleared successfully.'));
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
// Go back
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
} catch (error) {
|
|
354
|
+
console.error(chalk.red('Error managing custom attributes:'), error);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Main menu
|
|
359
|
+
async function mainMenu() {
|
|
360
|
+
const option = await showMenu('Firebase Auth User Management', [
|
|
361
|
+
'List all users',
|
|
362
|
+
'Create a new user',
|
|
363
|
+
'Manage custom attributes',
|
|
364
|
+
'Exit'
|
|
365
|
+
]);
|
|
366
|
+
|
|
367
|
+
switch (option) {
|
|
368
|
+
case 1:
|
|
369
|
+
await listUsers();
|
|
370
|
+
break;
|
|
371
|
+
case 2:
|
|
372
|
+
await createUser();
|
|
373
|
+
break;
|
|
374
|
+
case 3:
|
|
375
|
+
await manageCustomAttributes();
|
|
376
|
+
break;
|
|
377
|
+
case 4:
|
|
378
|
+
console.log(chalk.green('\nExiting. Goodbye!'));
|
|
379
|
+
rl.close();
|
|
380
|
+
process.exit(0);
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Return to main menu
|
|
385
|
+
await mainMenu();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Start the script
|
|
389
|
+
async function start() {
|
|
390
|
+
console.log(chalk.cyan('\nFirebase Auth User Management (DEVELOPMENT ONLY)'));
|
|
391
|
+
|
|
392
|
+
initializeFirebase();
|
|
393
|
+
await mainMenu();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Handle errors and cleanup
|
|
397
|
+
process.on('uncaughtException', (error) => {
|
|
398
|
+
console.error(chalk.red('Uncaught exception:'), error);
|
|
399
|
+
rl.close();
|
|
400
|
+
process.exit(1);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
404
|
+
console.error(chalk.red('Unhandled rejection at:'), promise, chalk.red('reason:'), reason);
|
|
405
|
+
rl.close();
|
|
406
|
+
process.exit(1);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// Start the script
|
|
410
|
+
start();
|