@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/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ // create-frontend is a CLI tool package with .js/.cjs scripts.
2
+ // No TypeScript modules to re-export.
@@ -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();