better-auth-studio 1.0.58 → 1.0.59-beta.10

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.
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAOA,OAAO,EAA+B,MAAM,EAAE,MAAM,SAAS,CAAC;AAS9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA8D9C,wBAAsB,oBAAoB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAyLhG;AAwBD,wBAAgB,YAAY,CAC1B,UAAU,EAAE,UAAU,EACtB,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAk8KR"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAeA,OAAO,EAA+B,MAAM,EAAE,MAAM,SAAS,CAAC;AAS9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA+D9C,wBAAsB,oBAAoB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAqLhG;AAeD,wBAAgB,YAAY,CAC1B,UAAU,EAAE,UAAU,EACtB,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CA4mLR"}
package/dist/routes.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createHmac, randomBytes } from 'node:crypto';
2
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { existsSync, readFileSync, writeFileSync, } from 'node:fs';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { fileURLToPath, pathToFileURL } from 'node:url';
5
5
  // @ts-expect-error
@@ -8,6 +8,7 @@ import { scryptAsync } from '@noble/hashes/scrypt.js';
8
8
  import { Router } from 'express';
9
9
  import { createJiti } from 'jiti';
10
10
  import { createMockAccount, createMockSession, createMockUser, createMockVerification, getAuthAdapter, } from './auth-adapter.js';
11
+ import { possiblePaths } from './config.js';
11
12
  import { getAuthData } from './data.js';
12
13
  import { initializeGeoService, resolveIPLocation, setGeoDbPath } from './geo-service.js';
13
14
  import { detectDatabaseWithDialect } from './utils/database-detection.js';
@@ -66,7 +67,6 @@ export async function safeImportAuthConfig(authConfigPath, noCache = false) {
66
67
  if (authConfigPath.endsWith('.ts')) {
67
68
  const aliases = {};
68
69
  const authConfigDir = dirname(authConfigPath);
69
- // Find project root by looking for tsconfig.json
70
70
  let projectDir = authConfigDir;
71
71
  let tsconfigPath = join(projectDir, 'tsconfig.json');
72
72
  while (!existsSync(tsconfigPath) && projectDir !== dirname(projectDir)) {
@@ -74,10 +74,8 @@ export async function safeImportAuthConfig(authConfigPath, noCache = false) {
74
74
  tsconfigPath = join(projectDir, 'tsconfig.json');
75
75
  }
76
76
  const content = readFileSync(authConfigPath, 'utf-8');
77
- // Get path aliases from tsconfig (including extends chain)
78
77
  const { getPathAliases } = await import('./config.js');
79
78
  const tsconfigAliases = getPathAliases(projectDir) || {};
80
- // Handle relative imports
81
79
  const relativeImportRegex = /import\s+.*?\s+from\s+['"](\.\/[^'"]+)['"]/g;
82
80
  const dynamicImportRegex = /import\s*\(\s*['"](\.\/[^'"]+)['"]\s*\)/g;
83
81
  const foundImports = new Set();
@@ -111,7 +109,6 @@ export async function safeImportAuthConfig(authConfigPath, noCache = false) {
111
109
  const pathAliasRegex = /import\s+.*?\s+from\s+['"](\$[^'"]+)['"]/g;
112
110
  while ((match = pathAliasRegex.exec(content)) !== null) {
113
111
  const aliasPath = match[1];
114
- // Check if we have a matching alias
115
112
  const aliasBase = aliasPath.split('/')[0];
116
113
  if (tsconfigAliases[aliasBase]) {
117
114
  const remainingPath = aliasPath.replace(aliasBase, '').replace(/^\//, '');
@@ -222,14 +219,6 @@ export async function safeImportAuthConfig(authConfigPath, noCache = false) {
222
219
  async function findAuthConfigPath() {
223
220
  const { join, dirname } = await import('node:path');
224
221
  const { existsSync } = await import('node:fs');
225
- const possiblePaths = [
226
- 'auth.js',
227
- 'auth.ts',
228
- 'src/auth.js',
229
- 'src/auth.ts',
230
- 'lib/auth.js',
231
- 'lib/auth.ts',
232
- ];
233
222
  for (const path of possiblePaths) {
234
223
  const fullPath = join(process.cwd(), path);
235
224
  if (existsSync(fullPath)) {
@@ -4354,15 +4343,44 @@ export function createRoutes(authConfig, configPath, geoDbPath) {
4354
4343
  const fields = table.fields
4355
4344
  ?.filter((f) => f.name.trim())
4356
4345
  .map((field) => {
4357
- let attrStr = `type: "${field.type}"`;
4358
- if (field.required)
4359
- attrStr += ',\n required: true';
4360
- if (field.unique)
4361
- attrStr += ',\n unique: true';
4346
+ const attrs = [`type: "${field.type}"`];
4347
+ attrs.push(`required: ${field.required ? 'true' : 'false'}`);
4348
+ attrs.push(`unique: ${field.unique ? 'true' : 'false'}`);
4349
+ attrs.push('input: false');
4350
+ // Handle defaultValue
4351
+ if (field.defaultValue !== undefined &&
4352
+ field.defaultValue !== null &&
4353
+ field.defaultValue !== '') {
4354
+ if (field.type === 'string') {
4355
+ attrs.push(`defaultValue: "${field.defaultValue}"`);
4356
+ }
4357
+ else if (field.type === 'boolean') {
4358
+ attrs.push(`defaultValue: ${field.defaultValue === 'true' || field.defaultValue === true}`);
4359
+ }
4360
+ else if (field.type === 'number') {
4361
+ attrs.push(`defaultValue: ${field.defaultValue}`);
4362
+ }
4363
+ else if (field.type === 'date') {
4364
+ if (field.defaultValue === 'now()') {
4365
+ attrs.push('defaultValue: new Date()');
4366
+ }
4367
+ else {
4368
+ attrs.push(`defaultValue: new Date("${field.defaultValue}")`);
4369
+ }
4370
+ }
4371
+ }
4372
+ else if (field.type === 'boolean') {
4373
+ // Default to false for boolean if no defaultValue specified
4374
+ attrs.push('defaultValue: false');
4375
+ }
4376
+ const attrStr = attrs.join(',\n ');
4362
4377
  return ` ${field.name}: {\n ${attrStr}\n }`;
4363
4378
  })
4364
4379
  .join(',\n') || '';
4365
- return ` ${table.name}: {
4380
+ const tableName = table.isExtending && table.extendedTableName
4381
+ ? table.extendedTableName.trim()
4382
+ : table.name.trim();
4383
+ return ` ${tableName}: {
4366
4384
  fields: {
4367
4385
  ${fields}
4368
4386
  },
@@ -4370,7 +4388,29 @@ ${fields}
4370
4388
  })
4371
4389
  .join(',\n')
4372
4390
  : '';
4373
- // Generate hooks
4391
+ const preserveIndentation = (code, baseIndent) => {
4392
+ if (!code.trim())
4393
+ return '';
4394
+ const lines = code.split('\n');
4395
+ const nonEmptyLines = lines.filter((line) => line.trim());
4396
+ if (nonEmptyLines.length === 0)
4397
+ return '';
4398
+ const minIndent = Math.min(...nonEmptyLines.map((line) => {
4399
+ const match = line.match(/^(\s*)/);
4400
+ return match ? match[1].length : 0;
4401
+ }));
4402
+ return lines
4403
+ .map((line) => {
4404
+ if (!line.trim())
4405
+ return '';
4406
+ const currentIndent = line.match(/^(\s*)/)?.[1] || '';
4407
+ const relativeIndent = Math.max(0, currentIndent.length - minIndent);
4408
+ const content = line.trim();
4409
+ return baseIndent + ' '.repeat(relativeIndent) + content;
4410
+ })
4411
+ .filter(Boolean)
4412
+ .join('\n');
4413
+ };
4374
4414
  const beforeHooks = hooks
4375
4415
  .filter((h) => h.timing === 'before')
4376
4416
  .map((hook) => {
@@ -4390,11 +4430,11 @@ ${fields}
4390
4430
  else {
4391
4431
  matcher = `(ctx) => true`;
4392
4432
  }
4433
+ const formattedHookLogic = preserveIndentation(hook.hookLogic || '// Hook logic here', ' ');
4393
4434
  return ` {
4394
4435
  matcher: ${matcher},
4395
4436
  handler: createAuthMiddleware(async (ctx) => {
4396
- // ${hook.name || `${hook.timing} ${hook.action} hook`}
4397
- ${hook.hookLogic || '// Hook logic here'}
4437
+ ${formattedHookLogic}
4398
4438
  }),
4399
4439
  }`;
4400
4440
  });
@@ -4417,27 +4457,25 @@ ${fields}
4417
4457
  else {
4418
4458
  matcher = `(ctx) => true`;
4419
4459
  }
4460
+ const formattedHookLogic = preserveIndentation(hook.hookLogic || '// Hook logic here', ' ');
4420
4461
  return ` {
4421
4462
  matcher: ${matcher},
4422
4463
  handler: createAuthMiddleware(async (ctx) => {
4423
- // ${hook.name || `${hook.timing} ${hook.action} hook`}
4424
- ${hook.hookLogic || '// Hook logic here'}
4464
+ ${formattedHookLogic}
4425
4465
  }),
4426
4466
  }`;
4427
4467
  });
4428
- // Generate middleware
4429
4468
  const middlewareCode = middleware
4430
4469
  .map((mw) => {
4470
+ const formattedMiddlewareLogic = preserveIndentation(mw.middlewareLogic || '// Middleware logic here', ' ');
4431
4471
  return ` {
4432
4472
  path: "${mw.path}",
4433
4473
  middleware: createAuthMiddleware(async (ctx) => {
4434
- // ${mw.name || 'Middleware'}
4435
- ${mw.middlewareLogic || '// Middleware logic here'}
4474
+ ${formattedMiddlewareLogic}
4436
4475
  }),
4437
4476
  }`;
4438
4477
  })
4439
4478
  .join(',\n');
4440
- // Generate endpoints
4441
4479
  const endpointsCode = endpoints.length > 0
4442
4480
  ? endpoints
4443
4481
  .map((endpoint) => {
@@ -4445,27 +4483,14 @@ ${fields}
4445
4483
  const sanitizedName = endpointName.replace(/[^a-zA-Z0-9]/g, '');
4446
4484
  const endpointPath = endpoint.path?.trim() || `/${camelCaseName}/${sanitizedName}`;
4447
4485
  const handlerLogic = endpoint.handlerLogic ||
4448
- '// Endpoint handler logic here\n return ctx.json({ success: true });';
4449
- const formattedHandlerLogic = handlerLogic
4450
- .split('\n')
4451
- .map((line) => {
4452
- const trimmed = line.trim();
4453
- if (!trimmed)
4454
- return '';
4455
- if (!line.startsWith(' ')) {
4456
- return ' ' + trimmed;
4457
- }
4458
- return line;
4459
- })
4460
- .filter(Boolean)
4461
- .join('\n');
4486
+ '// Endpoint handler logic here\nreturn ctx.json({ success: true });';
4487
+ const formattedHandlerLogic = preserveIndentation(handlerLogic, ' ');
4462
4488
  return ` ${sanitizedName}: createAuthEndpoint(
4463
4489
  "${endpointPath}",
4464
4490
  {
4465
- method: "${endpoint.method || 'POST'}" as const,
4491
+ method: "${endpoint.method || 'POST'}",
4466
4492
  },
4467
4493
  async (ctx) => {
4468
- // ${endpoint.name || sanitizedName}
4469
4494
  ${formattedHandlerLogic}
4470
4495
  },
4471
4496
  ),`;
@@ -4531,14 +4556,13 @@ ${formattedHandlerLogic}
4531
4556
  if (rateLimitCode) {
4532
4557
  pluginParts.push(` rateLimit: {\n${rateLimitCode}\n }`);
4533
4558
  }
4534
- // Generate server plugin code
4535
4559
  const imports = ['import type { BetterAuthPlugin } from "@better-auth/core"'];
4536
4560
  if (hooks.length > 0 || middleware.length > 0 || endpoints.length > 0) {
4537
4561
  imports.push('import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api"');
4538
4562
  }
4539
4563
  const serverPluginBody = pluginParts.length > 0
4540
- ? ` id: "${camelCaseName}" as const,\n${pluginParts.join(',\n')}`
4541
- : ` id: "${camelCaseName}" as const`;
4564
+ ? ` id: "${camelCaseName}",\n${pluginParts.join(',\n')}`
4565
+ : ` id: "${camelCaseName}"`;
4542
4566
  const serverPluginCode = cleanCode(`import type { BetterAuthPlugin } from "@better-auth/core";
4543
4567
  ${imports.join('\n')}
4544
4568
 
@@ -4578,12 +4602,11 @@ import type { ${camelCaseName} } from "..";
4578
4602
 
4579
4603
  export const ${camelCaseName}Client = () => {
4580
4604
  return {
4581
- id: "${camelCaseName}" as const,
4605
+ id: "${camelCaseName}",
4582
4606
  $InferServerPlugin: {} as ReturnType<typeof ${camelCaseName}>,${pathMethods ? `\n pathMethods: {\n${pathMethods}\n },` : ''}${atomListenersCode}
4583
4607
  } satisfies BetterAuthClientPlugin;
4584
4608
  };
4585
4609
  `);
4586
- // Generate server setup code
4587
4610
  const serverSetupCode = cleanCode(`import { betterAuth } from "@better-auth/core";
4588
4611
  import { ${camelCaseName} } from "./plugin/${camelCaseName}";
4589
4612
 
@@ -4601,7 +4624,6 @@ export const auth = betterAuth({
4601
4624
  vue: 'better-auth/vue',
4602
4625
  };
4603
4626
  const frameworkImport = frameworkImportMap[clientFramework] || 'better-auth/react';
4604
- // Get baseURL based on framework
4605
4627
  const baseURLMap = {
4606
4628
  react: 'process.env.NEXT_PUBLIC_BETTER_AUTH_URL || "http://localhost:3000"',
4607
4629
  svelte: 'import.meta.env.PUBLIC_BETTER_AUTH_URL || "http://localhost:5173"',
@@ -4985,6 +5007,110 @@ export const authClient = createAuthClient({
4985
5007
  });
4986
5008
  }
4987
5009
  });
5010
+ router.post('/api/tools/check-env-secret', async (_req, res) => {
5011
+ try {
5012
+ const envPath = join(process.cwd(), '.env');
5013
+ const envLocalPath = join(process.cwd(), '.env.local');
5014
+ const targetPath = existsSync(envLocalPath) ? envLocalPath : envPath;
5015
+ const envContent = existsSync(targetPath) ? readFileSync(targetPath, 'utf-8') : '';
5016
+ const envLines = envContent.split('\n');
5017
+ let existingSecret;
5018
+ envLines.forEach((line) => {
5019
+ const trimmed = line.trim();
5020
+ if (!trimmed || trimmed.startsWith('#'))
5021
+ return;
5022
+ const match = trimmed.match(/^BETTER_AUTH_SECRET\s*=\s*(.*)$/i);
5023
+ if (match) {
5024
+ let value = match[1].trim();
5025
+ if (value.length >= 2) {
5026
+ if ((value.startsWith('"') && value.endsWith('"')) ||
5027
+ (value.startsWith("'") && value.endsWith("'"))) {
5028
+ value = value.slice(1, -1).trim();
5029
+ }
5030
+ }
5031
+ if (value && value.trim() !== '') {
5032
+ existingSecret = value;
5033
+ }
5034
+ }
5035
+ });
5036
+ const hasExisting = !!existingSecret && existingSecret.trim() !== '';
5037
+ res.json({
5038
+ success: true,
5039
+ hasExisting,
5040
+ existingSecret: hasExisting ? existingSecret : undefined,
5041
+ path: targetPath,
5042
+ });
5043
+ }
5044
+ catch (error) {
5045
+ res.status(500).json({
5046
+ success: false,
5047
+ message: error instanceof Error ? error.message : 'Failed to check secret',
5048
+ });
5049
+ }
5050
+ });
5051
+ router.post('/api/tools/write-env-secret', async (req, res) => {
5052
+ try {
5053
+ const { secret, action = 'override' } = req.body || {};
5054
+ if (!secret) {
5055
+ return res.status(400).json({
5056
+ success: false,
5057
+ message: 'Secret is required',
5058
+ });
5059
+ }
5060
+ const envPath = join(process.cwd(), '.env');
5061
+ const envLocalPath = join(process.cwd(), '.env.local');
5062
+ const targetPath = existsSync(envLocalPath) ? envLocalPath : envPath;
5063
+ const envContent = existsSync(targetPath) ? readFileSync(targetPath, 'utf-8') : '';
5064
+ const envLines = envContent.split('\n');
5065
+ const envMap = new Map();
5066
+ const newLines = [];
5067
+ envLines.forEach((line, index) => {
5068
+ const trimmed = line.trim();
5069
+ if (!trimmed || trimmed.startsWith('#')) {
5070
+ newLines.push(line);
5071
+ return;
5072
+ }
5073
+ const match = trimmed.match(/^([^=#]+)\s*=\s*(.*)$/);
5074
+ if (match) {
5075
+ const key = match[1].trim();
5076
+ if (key.toUpperCase() === 'BETTER_AUTH_SECRET') {
5077
+ envMap.set('BETTER_AUTH_SECRET', { line, index });
5078
+ if (action === 'override') {
5079
+ newLines.push(`BETTER_AUTH_SECRET=${secret}`);
5080
+ }
5081
+ else {
5082
+ newLines.push(line);
5083
+ }
5084
+ }
5085
+ else {
5086
+ newLines.push(line);
5087
+ }
5088
+ }
5089
+ else {
5090
+ newLines.push(line);
5091
+ }
5092
+ });
5093
+ if (!envMap.has('BETTER_AUTH_SECRET')) {
5094
+ if (newLines.length > 0 && newLines[newLines.length - 1].trim() !== '') {
5095
+ newLines.push('');
5096
+ }
5097
+ newLines.push(`BETTER_AUTH_SECRET=${secret}`);
5098
+ }
5099
+ const newContent = newLines.join('\n');
5100
+ writeFileSync(targetPath, newContent, 'utf-8');
5101
+ res.json({
5102
+ success: true,
5103
+ message: 'Secret written successfully',
5104
+ path: targetPath,
5105
+ });
5106
+ }
5107
+ catch (error) {
5108
+ res.status(500).json({
5109
+ success: false,
5110
+ message: error instanceof Error ? error.message : 'Failed to write secret to .env',
5111
+ });
5112
+ }
5113
+ });
4988
5114
  router.post('/api/tools/check-env-credentials', async (req, res) => {
4989
5115
  try {
4990
5116
  const { provider } = req.body || {};