better-auth-studio 1.0.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.
Files changed (72) hide show
  1. package/README.md +1 -0
  2. package/dist/auth-adapter.d.ts +24 -0
  3. package/dist/auth-adapter.d.ts.map +1 -0
  4. package/dist/auth-adapter.js +481 -0
  5. package/dist/auth-adapter.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +49 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/config.d.ts +25 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +308 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/data.d.ts +38 -0
  15. package/dist/data.d.ts.map +1 -0
  16. package/dist/data.js +275 -0
  17. package/dist/data.js.map +1 -0
  18. package/dist/routes.d.ts +3 -0
  19. package/dist/routes.d.ts.map +1 -0
  20. package/dist/routes.js +1490 -0
  21. package/dist/routes.js.map +1 -0
  22. package/dist/studio.d.ts +10 -0
  23. package/dist/studio.d.ts.map +1 -0
  24. package/dist/studio.js +70 -0
  25. package/dist/studio.js.map +1 -0
  26. package/frontend/index.html +13 -0
  27. package/frontend/package-lock.json +4675 -0
  28. package/frontend/package.json +52 -0
  29. package/frontend/pnpm-lock.yaml +4020 -0
  30. package/frontend/postcss.config.js +6 -0
  31. package/frontend/src/App.tsx +36 -0
  32. package/frontend/src/components/CommandPalette.tsx +219 -0
  33. package/frontend/src/components/Layout.tsx +159 -0
  34. package/frontend/src/components/ui/badge.tsx +40 -0
  35. package/frontend/src/components/ui/button.tsx +53 -0
  36. package/frontend/src/components/ui/card.tsx +78 -0
  37. package/frontend/src/components/ui/input.tsx +20 -0
  38. package/frontend/src/components/ui/label.tsx +19 -0
  39. package/frontend/src/components/ui/select.tsx +71 -0
  40. package/frontend/src/index.css +130 -0
  41. package/frontend/src/lib/utils.ts +6 -0
  42. package/frontend/src/main.tsx +10 -0
  43. package/frontend/src/pages/Dashboard.tsx +231 -0
  44. package/frontend/src/pages/OrganizationDetails.tsx +1281 -0
  45. package/frontend/src/pages/Organizations.tsx +874 -0
  46. package/frontend/src/pages/Sessions.tsx +623 -0
  47. package/frontend/src/pages/Settings.tsx +1019 -0
  48. package/frontend/src/pages/TeamDetails.tsx +666 -0
  49. package/frontend/src/pages/Users.tsx +728 -0
  50. package/frontend/tailwind.config.js +75 -0
  51. package/frontend/tsconfig.json +31 -0
  52. package/frontend/tsconfig.node.json +10 -0
  53. package/frontend/vite.config.ts +31 -0
  54. package/package.json +59 -0
  55. package/public/assets/main-C-TXCXVW.css +1 -0
  56. package/public/assets/main-CCzTTP3P.js +296 -0
  57. package/public/index.html +14 -0
  58. package/src/auth-adapter.ts +471 -0
  59. package/src/cli.ts +51 -0
  60. package/src/config.ts +318 -0
  61. package/src/data.ts +351 -0
  62. package/src/routes.ts +1585 -0
  63. package/src/studio.ts +86 -0
  64. package/test-project/README.md +0 -0
  65. package/test-project/better-auth.db +0 -0
  66. package/test-project/better-auth_migrations/2025-08-27T15-55-04.099Z.sql +7 -0
  67. package/test-project/better-auth_migrations/2025-09-04T02-33-19.422Z.sql +7 -0
  68. package/test-project/package.json +29 -0
  69. package/test-project/pnpm-lock.yaml +1728 -0
  70. package/test-project/src/auth.ts +47 -0
  71. package/test-project/src/index.ts +40 -0
  72. package/tsconfig.json +21 -0
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Better Auth Studio</title>
8
+ <script type="module" crossorigin src="/assets/main-CCzTTP3P.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/main-C-TXCXVW.css">
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ </body>
14
+ </html>
@@ -0,0 +1,471 @@
1
+ import { join, dirname } from 'path';
2
+ import { existsSync } from 'fs';
3
+
4
+ export interface AuthAdapter {
5
+ createUser: (data: any) => Promise<any>;
6
+ createSession: (data: any) => Promise<any>;
7
+ createAccount: (data: any) => Promise<any>;
8
+ createVerification: (data: any) => Promise<any>;
9
+ createOrganization: (data: any) => Promise<any>;
10
+ create?: (...args: any[]) => Promise<any>;
11
+ update?: (...args: any[]) => Promise<any>;
12
+ delete?: (...args: any[]) => Promise<any>;
13
+ getUsers?: () => Promise<any[]>;
14
+ getSessions?: () => Promise<any[]>;
15
+ findMany?: (options: { model: string; where?: any; limit?: number; offset?: number }) => Promise<any[]>;
16
+ }
17
+
18
+ let authInstance: any = null;
19
+ let authAdapter: AuthAdapter | null = null;
20
+
21
+ export async function getAuthAdapter(): Promise<AuthAdapter | null> {
22
+ try {
23
+ const authConfigPath = await findAuthConfigPath();
24
+ if (!authConfigPath) {
25
+ console.warn('No auth config found');
26
+ return await createMockAdapter();
27
+ }
28
+
29
+ console.log('Loading auth instance from:', authConfigPath);
30
+
31
+ let authModule;
32
+ try {
33
+ authModule = await import(authConfigPath);
34
+ } catch (error) {
35
+ console.warn('Error importing auth module:', error);
36
+ return await createMockAdapter();
37
+ }
38
+
39
+ const auth = authModule.auth || authModule.default;
40
+ console.log({auth})
41
+ if (!auth) {
42
+ console.warn('No auth export found in config');
43
+ return await createMockAdapter();
44
+ }
45
+
46
+ let adapter;
47
+ try {
48
+ const context = await auth.$context;
49
+ adapter = context?.adapter;
50
+ } catch (error) {
51
+ console.warn('Error getting auth context:', error);
52
+ adapter = auth.adapter;
53
+ }
54
+
55
+ if (!adapter) {
56
+ console.warn('No adapter found in auth instance');
57
+ console.log('Auth object keys:', Object.keys(auth));
58
+ console.log('Falling back to mock adapter...');
59
+ return await createMockAdapter();
60
+ }
61
+
62
+ console.log('Adapter found, checking available methods...');
63
+ console.log('Adapter keys:', Object.keys(adapter));
64
+ console.log('Adapter methods:', Object.getOwnPropertyNames(Object.getPrototypeOf(adapter)));
65
+
66
+ if (adapter.options) {
67
+ console.log('Adapter options:', adapter.options);
68
+ }
69
+ if (adapter.createSchema) {
70
+ console.log('Adapter schema:', adapter.createSchema);
71
+ }
72
+
73
+ try {
74
+ if (adapter.schema) {
75
+ console.log('Adapter schema models:', Object.keys(adapter.schema));
76
+ }
77
+ if (adapter.models) {
78
+ console.log('Adapter models:', Object.keys(adapter.models));
79
+ }
80
+ if (adapter.tables) {
81
+ console.log('Adapter tables:', Object.keys(adapter.tables));
82
+ }
83
+ } catch (error) {
84
+ console.log('Could not inspect adapter schema:', error);
85
+ }
86
+
87
+
88
+ console.log('Using real adapter with model names');
89
+ authAdapter = {
90
+ createUser: async (data: any) => {
91
+ try {
92
+ const user = await adapter.create({
93
+ model: "user",
94
+ data: {
95
+ createdAt: new Date(),
96
+ updatedAt: new Date(),
97
+ emailVerified: false,
98
+ name: data.name,
99
+ email: data.email?.toLowerCase(),
100
+ image: data.image || `https://api.dicebear.com/7.x/avataaars/svg?seed=${data.email}`,
101
+ }
102
+ });
103
+
104
+ if (data.password) {
105
+ try {
106
+ await adapter.create({
107
+ model: "account",
108
+ data: {
109
+ userId: user.id,
110
+ providerId: "credential",
111
+ accountId: user.id,
112
+ password: data.password,
113
+ createdAt: new Date(),
114
+ updatedAt: new Date(),
115
+ }
116
+ });
117
+ console.log('Credential account created for user:', user.id);
118
+ } catch (accountError) {
119
+ console.error('Error creating credential account:', accountError);
120
+ }
121
+ }
122
+
123
+ console.log('User created via real adapter:', user);
124
+ return user;
125
+ } catch (error) {
126
+ console.error('Error creating user via real adapter:', error);
127
+ const mockAdapter = await createMockAdapter();
128
+ return await mockAdapter.createUser(data);
129
+ }
130
+ },
131
+ createSession: async (data: any) => {
132
+ try {
133
+ const session = await adapter.create({
134
+ model: "session",
135
+ data: {
136
+ createdAt: new Date(),
137
+ updatedAt: new Date(),
138
+ ...data,
139
+ }
140
+ });
141
+ console.log('Session created via real adapter:', session);
142
+ return session;
143
+ } catch (error) {
144
+ console.error('Error creating session via real adapter:', error);
145
+ const mockAdapter = await createMockAdapter();
146
+ return await mockAdapter.createSession(data);
147
+ }
148
+ },
149
+ createAccount: async (data: any) => {
150
+ try {
151
+ const account = await adapter.create({
152
+ model: "account",
153
+ data: {
154
+ createdAt: new Date(),
155
+ updatedAt: new Date(),
156
+ ...data,
157
+ }
158
+ });
159
+ console.log('Account created via real adapter:', account);
160
+ return account;
161
+ } catch (error) {
162
+ console.error('Error creating account via real adapter:', error);
163
+ const mockAdapter = await createMockAdapter();
164
+ return await mockAdapter.createAccount(data);
165
+ }
166
+ },
167
+ createVerification: async (data: any) => {
168
+ try {
169
+ const verification = await adapter.create({
170
+ model: "verification",
171
+ data: {
172
+ createdAt: new Date(),
173
+ updatedAt: new Date(),
174
+ ...data,
175
+ }
176
+ });
177
+ console.log('Verification created via real adapter:', verification);
178
+ return verification;
179
+ } catch (error) {
180
+ console.error('Error creating verification via real adapter:', error);
181
+ const mockAdapter = await createMockAdapter();
182
+ return await mockAdapter.createVerification(data);
183
+ }
184
+ },
185
+ createOrganization: async (data: any) => {
186
+ try {
187
+ const organization = await adapter.create({
188
+ model: "organization",
189
+ data: {
190
+ createdAt: new Date(),
191
+ updatedAt: new Date(),
192
+ ...data,
193
+ }
194
+ });
195
+ console.log('Organization created via real adapter:', organization);
196
+ return organization;
197
+ } catch (error) {
198
+ console.error('Error creating organization via real adapter:', error);
199
+ const mockAdapter = await createMockAdapter();
200
+ return await mockAdapter.createOrganization(data);
201
+ }
202
+ },
203
+ getUsers: async () => {
204
+ try {
205
+ if (typeof adapter.findMany === 'function') {
206
+ const users = await adapter.findMany({ model: 'user' });
207
+ console.log('Found users via findMany:', users?.length || 0);
208
+ return users || [];
209
+ }
210
+ if (typeof adapter.getUsers === 'function') {
211
+ const users = await adapter.getUsers();
212
+ console.log('Found users via getUsers:', users?.length || 0);
213
+ return users || [];
214
+ }
215
+ console.log('No read method available on adapter, returning mock data');
216
+ return [];
217
+ } catch (error) {
218
+ console.error('Error getting users from adapter:', error);
219
+ return [];
220
+ }
221
+ },
222
+ getSessions: async () => {
223
+ try {
224
+ if (typeof adapter.findMany === 'function') {
225
+ const sessions = await adapter.findMany({ model: 'session' });
226
+ return sessions || [];
227
+ }
228
+ if (typeof adapter.getSessions === 'function') {
229
+ const sessions = await adapter.getSessions();
230
+ return sessions || [];
231
+ }
232
+ return [];
233
+ } catch (error) {
234
+ console.error('Error getting sessions from adapter:', error);
235
+ return [];
236
+ }
237
+ },
238
+ findMany: async (options: { model: string; where?: any; limit?: number; offset?: number }) => {
239
+ try {
240
+ if (typeof adapter.findMany === 'function') {
241
+ return await adapter.findMany(options);
242
+ }
243
+ return [];
244
+ } catch (error) {
245
+ console.error('Error using findMany on adapter:', error);
246
+ return [];
247
+ }
248
+ }
249
+ };
250
+ const adapters = {...adapter, ...authAdapter};
251
+ return adapters;
252
+ } catch (error) {
253
+ console.error('Error loading auth adapter:', error);
254
+ console.log('Falling back to mock adapter due to error...');
255
+ return await createMockAdapter();
256
+ }
257
+ }
258
+
259
+ async function findAuthConfigPath(): Promise<string | null> {
260
+ const possibleConfigFiles = [
261
+ 'auth.ts',
262
+ 'auth.js',
263
+ 'src/auth.ts',
264
+ 'src/auth.js',
265
+ 'better-auth.config.ts',
266
+ 'better-auth.config.js',
267
+ 'better-auth.config.json',
268
+ 'auth.config.ts',
269
+ 'auth.config.js',
270
+ 'auth.config.json',
271
+ 'studio-config.json' // Move this to the end as fallback
272
+ ];
273
+
274
+ let currentDir = process.cwd();
275
+ const maxDepth = 10;
276
+ let depth = 0;
277
+
278
+ while (currentDir && depth < maxDepth) {
279
+ for (const configFile of possibleConfigFiles) {
280
+ const configPath = join(currentDir, configFile);
281
+
282
+ if (existsSync(configPath)) {
283
+ return configPath;
284
+ }
285
+ }
286
+
287
+ const parentDir = dirname(currentDir);
288
+ if (parentDir === currentDir) {
289
+ break;
290
+ }
291
+ currentDir = parentDir;
292
+ depth++;
293
+ }
294
+
295
+ return null;
296
+ }
297
+
298
+ export async function createMockUser(adapter: AuthAdapter, index: number) {
299
+ const userData = {
300
+ email: `user${index}@example.com`,
301
+ name: `User ${index}`,
302
+ emailVerified: true,
303
+ image: `https://api.dicebear.com/7.x/avataaars/svg?seed=user${index}`,
304
+ createdAt: new Date(),
305
+ updatedAt: new Date()
306
+ };
307
+
308
+ return await adapter.createUser(userData);
309
+ }
310
+
311
+ export async function createMockSession(adapter: AuthAdapter, userId: string, index: number) {
312
+ const sessionData = {
313
+ userId: userId,
314
+ expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days from now
315
+ sessionToken: `session_token_${index}_${Date.now()}`,
316
+ createdAt: new Date(),
317
+ updatedAt: new Date()
318
+ };
319
+
320
+ return await adapter.createSession(sessionData);
321
+ }
322
+
323
+ export async function createMockAccount(adapter: AuthAdapter, userId: string, index: number) {
324
+ const accountData = {
325
+ userId: userId,
326
+ type: 'oauth',
327
+ provider: 'github',
328
+ providerAccountId: `github_${index}`,
329
+ refresh_token: `refresh_token_${index}`,
330
+ access_token: `access_token_${index}`,
331
+ expires_at: Math.floor(Date.now() / 1000) + 3600,
332
+ token_type: 'bearer',
333
+ scope: 'read:user',
334
+ id_token: `id_token_${index}`,
335
+ session_state: `session_state_${index}`,
336
+ createdAt: new Date(),
337
+ updatedAt: new Date()
338
+ };
339
+
340
+ return await adapter.createAccount(accountData);
341
+ }
342
+
343
+ export async function createMockVerification(adapter: AuthAdapter, userId: string, index: number) {
344
+ const verificationData = {
345
+ identifier: `user${index}@example.com`,
346
+ token: `verification_token_${index}_${Date.now()}`,
347
+ expires: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours from now
348
+ createdAt: new Date(),
349
+ updatedAt: new Date()
350
+ };
351
+
352
+ return await adapter.createVerification(verificationData);
353
+ }
354
+
355
+ async function createMockAdapter(): Promise<AuthAdapter> {
356
+ console.log('Creating mock adapter for development/testing');
357
+
358
+ return {
359
+ createUser: async (data: any) => {
360
+ const mockUser = {
361
+ id: `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
362
+ ...data,
363
+ createdAt: new Date(),
364
+ updatedAt: new Date()
365
+ };
366
+ console.log('Mock user created:', mockUser);
367
+ return mockUser;
368
+ },
369
+ createSession: async (data: any) => {
370
+ const mockSession = {
371
+ id: `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
372
+ ...data,
373
+ createdAt: new Date(),
374
+ updatedAt: new Date()
375
+ };
376
+ console.log('Mock session created:', mockSession);
377
+ return mockSession;
378
+ },
379
+ createAccount: async (data: any) => {
380
+ const mockAccount = {
381
+ id: `account_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
382
+ ...data,
383
+ createdAt: new Date(),
384
+ updatedAt: new Date()
385
+ };
386
+ console.log('Mock account created:', mockAccount);
387
+ return mockAccount;
388
+ },
389
+ createVerification: async (data: any) => {
390
+ const mockVerification = {
391
+ id: `verification_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
392
+ ...data,
393
+ createdAt: new Date(),
394
+ updatedAt: new Date()
395
+ };
396
+ console.log('Mock verification created:', mockVerification);
397
+ return mockVerification;
398
+ },
399
+ createOrganization: async (data: any) => {
400
+ const mockOrganization = {
401
+ id: `org_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
402
+ ...data,
403
+ createdAt: new Date(),
404
+ updatedAt: new Date()
405
+ };
406
+ console.log('Mock organization created:', mockOrganization);
407
+ return mockOrganization;
408
+ },
409
+ getUsers: async () => {
410
+ const mockUsers = [
411
+ {
412
+ id: 'user_1',
413
+ email: 'user1@example.com',
414
+ name: 'User 1',
415
+ emailVerified: true,
416
+ image: 'https://api.dicebear.com/7.x/avataaars/svg?seed=user1',
417
+ createdAt: new Date().toISOString(),
418
+ updatedAt: new Date().toISOString(),
419
+ provider: 'email',
420
+ lastSignIn: new Date().toISOString(),
421
+ status: 'active'
422
+ },
423
+ {
424
+ id: 'user_2',
425
+ email: 'user2@example.com',
426
+ name: 'User 2',
427
+ emailVerified: true,
428
+ image: 'https://api.dicebear.com/7.x/avataaars/svg?seed=user2',
429
+ createdAt: new Date().toISOString(),
430
+ updatedAt: new Date().toISOString(),
431
+ provider: 'github',
432
+ lastSignIn: new Date().toISOString(),
433
+ status: 'active'
434
+ }
435
+ ];
436
+ return mockUsers;
437
+ },
438
+ getSessions: async () => {
439
+ const mockSessions = [
440
+ {
441
+ id: 'session_1',
442
+ userId: 'user_1',
443
+ expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
444
+ createdAt: new Date().toISOString(),
445
+ updatedAt: new Date().toISOString(),
446
+ sessionToken: 'session_token_1'
447
+ }
448
+ ];
449
+ return mockSessions;
450
+ },
451
+ findMany: async (options: { model: string; where?: any; limit?: number; offset?: number }) => {
452
+ if (options.model === 'user') {
453
+ return [
454
+ {
455
+ id: 'user_1',
456
+ email: 'user1@example.com',
457
+ name: 'User 1',
458
+ emailVerified: true,
459
+ image: 'https://api.dicebear.com/7.x/avataaars/svg?seed=user1',
460
+ createdAt: new Date().toISOString(),
461
+ updatedAt: new Date().toISOString(),
462
+ provider: 'email',
463
+ lastSignIn: new Date().toISOString(),
464
+ status: 'active'
465
+ }
466
+ ];
467
+ }
468
+ return [];
469
+ }
470
+ };
471
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import { startStudio } from './studio';
6
+ import { findAuthConfig } from './config';
7
+
8
+ const program = new Command();
9
+
10
+ program
11
+ .name('better-auth-studio')
12
+ .description('Better Auth Studio - GUI dashboard for Better Auth')
13
+ .version('1.0.0');
14
+
15
+ program
16
+ .command('studio')
17
+ .description('Start Better Auth Studio')
18
+ .option('-p, --port <port>', 'Port to run the studio on', '3001')
19
+ .option('-h, --host <host>', 'Host to run the studio on', 'localhost')
20
+ .option('--no-open', 'Do not open browser automatically')
21
+ .action(async (options) => {
22
+ try {
23
+ console.log(chalk.blue('🔐 Better Auth Studio'));
24
+ console.log(chalk.gray('Starting Better Auth Studio...\n'));
25
+
26
+ const authConfig = await findAuthConfig();
27
+ if (!authConfig) {
28
+ console.error(chalk.red('❌ No Better Auth configuration found.'));
29
+ console.log(chalk.yellow('Make sure you have a Better Auth configuration file in your project.'));
30
+ console.log(chalk.yellow('Supported files: auth.ts, auth.js, better-auth.config.ts, etc.'));
31
+ process.exit(1);
32
+ }
33
+
34
+ console.log(chalk.green('✅ Found Better Auth configuration'));
35
+ console.log(chalk.gray(`Database: ${authConfig.database?.type || 'Not configured'}`));
36
+ console.log(chalk.gray(`Providers: ${authConfig.providers?.map(p => p.type).join(', ') || 'None'}\n`));
37
+
38
+ await startStudio({
39
+ port: parseInt(options.port),
40
+ host: options.host,
41
+ openBrowser: options.open,
42
+ authConfig
43
+ });
44
+
45
+ } catch (error) {
46
+ console.error(chalk.red('❌ Failed to start Better Auth Studio:'), error);
47
+ process.exit(1);
48
+ }
49
+ });
50
+
51
+ program.parse();