@soapjs/cli 1.0.0 → 1.0.2
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 +3 -0
- package/build/cli.js +2 -3
- package/build/commands/add/add.command.js +1 -2
- package/build/commands/add/command-plan.js +7 -8
- package/build/commands/add/entity-plan.js +1 -2
- package/build/commands/add/event-plan.js +1 -2
- package/build/commands/add/query-plan.js +6 -7
- package/build/commands/add/repository-plan.js +8 -9
- package/build/commands/add/resource-plan.js +20 -21
- package/build/commands/add/route-plan.js +7 -7
- package/build/commands/add/socket-plan.js +2 -3
- package/build/commands/add/use-case-plan.js +2 -3
- package/build/commands/check/check.command.js +1 -2
- package/build/commands/create/create.command.js +2 -3
- package/build/commands/create/project-plan.js +272 -227
- package/build/commands/doctor/doctor.command.js +1 -2
- package/build/commands/generate/bruno-analysis.js +4 -5
- package/build/commands/generate/bruno-plan.js +1 -2
- package/build/commands/generate/generate.command.js +1 -2
- package/build/commands/info/info.command.js +1 -2
- package/build/commands/remove/remove.command.js +1 -2
- package/build/commands/shared/common-options.js +3 -4
- package/build/commands/update/update.command.js +1 -2
- package/build/config/auth-policy.js +3 -4
- package/build/config/find-soap-root.js +1 -2
- package/build/config/load-soap-config.js +1 -2
- package/build/config/schemas/types.d.ts +1 -1
- package/build/config/schemas/validation.js +5 -6
- package/build/config/write-soap-config.js +1 -2
- package/build/core/command-context.js +2 -3
- package/build/core/errors.js +2 -2
- package/build/core/output.js +1 -2
- package/build/core/result.js +2 -3
- package/build/dependencies/dependency-resolver.js +12 -17
- package/build/dependencies/package-manager.js +3 -4
- package/build/io/conflict-policy.js +3 -4
- package/build/io/file-writer.js +1 -2
- package/build/io/format-file.js +1 -2
- package/build/presets/create-presets.js +3 -3
- package/build/prompts/add-resource.prompt.js +1 -2
- package/build/prompts/add-route.prompt.js +1 -2
- package/build/prompts/create-project.prompt.js +3 -2
- package/build/prompts/generate-bruno.prompt.js +1 -2
- package/build/prompts/inquirer-prompt-adapter.js +14 -5
- package/build/registry/registry.service.js +5 -6
- package/build/summary/create-summary.js +1 -2
- package/build/templates/naming.js +2 -3
- package/build/templates/template-engine.js +1 -2
- package/build/templates/template-resolver.js +2 -3
- package/build/terminal/terminal-capabilities.js +2 -3
- package/docs/cli/create.md +4 -1
- package/docs/guides/auth.md +9 -1
- package/docs/guides/index.md +1 -0
- package/docs/plans/KAFKA_1_0_UPDATE.md +30 -0
- package/package.json +3 -3
|
@@ -3,7 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.zonesOptions = exports.contractsOptions = exports.docsOptions = exports.telemetryOptions = exports.realtimeOptions = exports.messagingOptions = exports.authOptions = exports.databaseOptions = void 0;
|
|
7
|
+
exports.createDefaultCapabilities = createDefaultCapabilities;
|
|
8
|
+
exports.createSoapConfigBundle = createSoapConfigBundle;
|
|
9
|
+
exports.parseCsvOption = parseCsvOption;
|
|
10
|
+
exports.targetRoot = targetRoot;
|
|
11
|
+
exports.createProjectFiles = createProjectFiles;
|
|
12
|
+
exports.createResourcesTs = createResourcesTs;
|
|
13
|
+
exports.createControllersTs = createControllersTs;
|
|
7
14
|
const path_1 = __importDefault(require("path"));
|
|
8
15
|
const naming_1 = require("../../templates/naming");
|
|
9
16
|
function createDefaultCapabilities() {
|
|
@@ -18,7 +25,6 @@ function createDefaultCapabilities() {
|
|
|
18
25
|
contracts: [],
|
|
19
26
|
};
|
|
20
27
|
}
|
|
21
|
-
exports.createDefaultCapabilities = createDefaultCapabilities;
|
|
22
28
|
function createSoapConfigBundle(plan) {
|
|
23
29
|
const defaultAuth = plan.capabilities.auth[0] ?? "none";
|
|
24
30
|
const brunoEnabled = plan.capabilities.apiClient.includes("bruno");
|
|
@@ -79,7 +85,6 @@ function createSoapConfigBundle(plan) {
|
|
|
79
85
|
},
|
|
80
86
|
};
|
|
81
87
|
}
|
|
82
|
-
exports.createSoapConfigBundle = createSoapConfigBundle;
|
|
83
88
|
function parseCsvOption(value, allowed) {
|
|
84
89
|
if (!value) {
|
|
85
90
|
return [];
|
|
@@ -93,13 +98,12 @@ function parseCsvOption(value, allowed) {
|
|
|
93
98
|
}
|
|
94
99
|
return Array.from(new Set(normalized));
|
|
95
100
|
}
|
|
96
|
-
exports.parseCsvOption = parseCsvOption;
|
|
97
101
|
exports.databaseOptions = ["mongo", "postgres", "mysql", "sqlite", "redis"];
|
|
98
102
|
const sqlDatabaseOptions = ["postgres", "mysql", "sqlite"];
|
|
99
103
|
exports.authOptions = ["jwt", "api-key", "local"];
|
|
100
104
|
exports.messagingOptions = ["in-memory", "kafka"];
|
|
101
105
|
exports.realtimeOptions = ["ws"];
|
|
102
|
-
exports.telemetryOptions = ["logs", "otel-noop"];
|
|
106
|
+
exports.telemetryOptions = ["logs", "otel-noop", "metrics", "memory"];
|
|
103
107
|
exports.docsOptions = ["openapi"];
|
|
104
108
|
exports.contractsOptions = ["zod"];
|
|
105
109
|
exports.zonesOptions = ["public", "private", "admin"];
|
|
@@ -109,7 +113,6 @@ function enabledSqlDatabases(capabilities) {
|
|
|
109
113
|
function targetRoot(cwd, name) {
|
|
110
114
|
return path_1.default.resolve(cwd, name);
|
|
111
115
|
}
|
|
112
|
-
exports.targetRoot = targetRoot;
|
|
113
116
|
function createProjectFiles(plan) {
|
|
114
117
|
const packageJson = {
|
|
115
118
|
name: plan.name,
|
|
@@ -243,11 +246,16 @@ function createProjectFiles(plan) {
|
|
|
243
246
|
...createBrunoFiles(plan),
|
|
244
247
|
];
|
|
245
248
|
}
|
|
246
|
-
exports.createProjectFiles = createProjectFiles;
|
|
247
249
|
function createIndexTs(plan) {
|
|
248
250
|
const docsImports = plan.capabilities.docs.includes("openapi")
|
|
249
251
|
? "import { DocumentationPlugin } from '@soapjs/soap-openapi';\n"
|
|
250
252
|
: "";
|
|
253
|
+
const expressImport = plan.capabilities.telemetry.includes("memory")
|
|
254
|
+
? "import { createApp, MemoryMonitoringPlugin } from '@soapjs/soap-express';"
|
|
255
|
+
: "import { createApp } from '@soapjs/soap-express';";
|
|
256
|
+
const authImport = plan.capabilities.auth.length > 0
|
|
257
|
+
? "import { createAuthRouter } from '@soapjs/soap-express/auth';\n"
|
|
258
|
+
: "";
|
|
251
259
|
const socketImport = plan.capabilities.realtime.includes("ws")
|
|
252
260
|
? "import { createSocketRuntime } from './common/sockets/socket.setup';\n"
|
|
253
261
|
: "";
|
|
@@ -256,32 +264,51 @@ function createIndexTs(plan) {
|
|
|
256
264
|
? `\n plugins: [\n {\n plugin: new DocumentationPlugin(),\n options: {\n info: {\n title: '${plan.name} API',\n version: '0.1.0',\n },\n servers: [{ url: \`http://localhost:\${config.port}\`, description: 'Local' }],\n interactivePath: '/docs',\n openApiPath: '/openapi.json',\n },\n },\n ],`
|
|
257
265
|
: "";
|
|
258
266
|
const cqrsOption = plan.architecture === "cqrs" ? "\n cqrs: true," : "";
|
|
267
|
+
const authRegistration = plan.capabilities.auth.length > 0
|
|
268
|
+
? `\n app.registerAuth(auth);\n app.getApp().use(createAuthRouter(auth, {\n basePath: '/auth',\n strategy: '${defaultAuthStrategy(plan)}',\n routes: {\n login: { path: '/auth/login', strategy: '${loginAuthStrategy(plan)}' },\n logout: { path: '/auth/logout', strategy: '${loginAuthStrategy(plan)}' },\n refresh: { path: '/auth/refresh', strategy: 'jwt', enabled: ${usesJwtAuth(plan)} },\n me: { path: '/auth/me', strategy: '${defaultAuthStrategy(plan)}' },\n verify: { path: '/auth/verify', strategy: '${defaultAuthStrategy(plan)}' },\n },\n }));\n`
|
|
269
|
+
: "";
|
|
270
|
+
const metrics = plan.capabilities.telemetry.includes("metrics")
|
|
271
|
+
? `\n app.useMetrics({\n enabled: true,\n exposeEndpoint: true,\n metricsPath: '/metrics',\n metricsFormat: 'prometheus',\n });\n`
|
|
272
|
+
: "";
|
|
273
|
+
const memory = plan.capabilities.telemetry.includes("memory")
|
|
274
|
+
? `\n const memoryMonitoringOptions = {\n enabled: true,\n exposeEndpoints: true,\n basePath: '/memory',\n includeInRequest: false,\n };\n let memoryMonitoringPlugin: MemoryMonitoringPlugin | undefined;\n\n try {\n app.useMemoryMonitoring(memoryMonitoringOptions);\n } catch (error) {\n memoryMonitoringPlugin = new MemoryMonitoringPlugin(memoryMonitoringOptions, logger);\n (memoryMonitoringPlugin as { version?: string }).version ??= '0.1.0';\n app.usePlugin(memoryMonitoringPlugin, memoryMonitoringOptions);\n }\n\n const getMemorySummary = () => app.getMemorySummary() ?? memoryMonitoringPlugin?.getMemorySummary();\n app.getApp().get('/memory', (_req, res) => res.json(getMemorySummary()));\n app.getApp().get('/memory/summary', (_req, res) => res.json(getMemorySummary()));\n app.getApp().get('/memory/health', (_req, res) => {\n const summary = getMemorySummary();\n res.status(summary?.status === 'critical' ? 503 : 200).json({ status: summary?.status ?? 'unknown', summary });\n });\n`
|
|
275
|
+
: "";
|
|
276
|
+
const socketRuntime = plan.capabilities.realtime.includes("ws")
|
|
277
|
+
? `\n const socketRuntime = createSocketRuntime(config, app.getServer());\n app.registerDrainable(socketRuntime);\n`
|
|
278
|
+
: "";
|
|
259
279
|
return `import 'reflect-metadata';
|
|
260
|
-
|
|
261
|
-
${docsImports}${socketImport}${cqrsImport}import './features';
|
|
280
|
+
${expressImport}
|
|
281
|
+
${docsImports}${authImport}${socketImport}${cqrsImport}import './features';
|
|
262
282
|
import { config } from './config/config';
|
|
263
283
|
import { controllers } from './config/controllers';
|
|
264
284
|
import { buildContainer } from './config/dependencies';
|
|
265
285
|
|
|
266
286
|
async function main(): Promise<void> {
|
|
267
|
-
const { container, drainables, logger,
|
|
287
|
+
const { container, drainables, logger, auth } = await buildContainer(config);
|
|
268
288
|
|
|
269
|
-
const app =
|
|
270
|
-
port: config.port,
|
|
289
|
+
const app = createApp({
|
|
271
290
|
container,
|
|
272
291
|
logger,
|
|
273
292
|
drainables,
|
|
274
293
|
controllers,
|
|
275
294
|
middleware: {
|
|
276
|
-
cors: true,
|
|
277
|
-
helmet: true,
|
|
278
295
|
logging: true,
|
|
279
296
|
compression: true,
|
|
280
297
|
},
|
|
281
|
-
|
|
298
|
+
app: {
|
|
299
|
+
security: ${createSecurityConfig(plan)},
|
|
300
|
+
},
|
|
282
301
|
healthCheck: true,${cqrsOption}${docsPlugin}
|
|
283
302
|
});
|
|
284
|
-
${
|
|
303
|
+
${authRegistration}${metrics}${memory}
|
|
304
|
+
|
|
305
|
+
const cqrsReady = (app as unknown as { cqrsReady?: Promise<void> }).cqrsReady;
|
|
306
|
+
if (cqrsReady) {
|
|
307
|
+
await cqrsReady;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
await app.start(config.port);
|
|
311
|
+
${socketRuntime}
|
|
285
312
|
|
|
286
313
|
logger.info('${plan.name} API ready', { port: config.port });
|
|
287
314
|
}
|
|
@@ -292,6 +319,41 @@ main().catch((error) => {
|
|
|
292
319
|
});
|
|
293
320
|
`;
|
|
294
321
|
}
|
|
322
|
+
function defaultAuthStrategy(plan) {
|
|
323
|
+
if (plan.capabilities.auth.includes("jwt"))
|
|
324
|
+
return "jwt";
|
|
325
|
+
if (plan.capabilities.auth.includes("local"))
|
|
326
|
+
return "local";
|
|
327
|
+
if (plan.capabilities.auth.includes("api-key"))
|
|
328
|
+
return "api-key";
|
|
329
|
+
return "jwt";
|
|
330
|
+
}
|
|
331
|
+
function loginAuthStrategy(plan) {
|
|
332
|
+
if (plan.capabilities.auth.includes("local") || plan.capabilities.auth.includes("jwt"))
|
|
333
|
+
return "local";
|
|
334
|
+
if (plan.capabilities.auth.includes("api-key"))
|
|
335
|
+
return "api-key";
|
|
336
|
+
return defaultAuthStrategy(plan);
|
|
337
|
+
}
|
|
338
|
+
function createSecurityConfig(plan) {
|
|
339
|
+
const throttle = plan.capabilities.auth.length > 0
|
|
340
|
+
? `{
|
|
341
|
+
global: { windowMs: 60_000, max: 300 },
|
|
342
|
+
routes: {
|
|
343
|
+
'POST /auth/login': { windowMs: 60_000, max: 5 },
|
|
344
|
+
'POST /auth/refresh': { windowMs: 60_000, max: 20, keyBy: 'user' },
|
|
345
|
+
'GET /auth/oauth/:provider/callback': { windowMs: 60_000, max: 30 },
|
|
346
|
+
},
|
|
347
|
+
}`
|
|
348
|
+
: "{ global: { windowMs: 60_000, max: 300 } }";
|
|
349
|
+
return `{
|
|
350
|
+
disablePoweredBy: true,
|
|
351
|
+
trustProxy: 1,
|
|
352
|
+
helmet: true,
|
|
353
|
+
cors: true,
|
|
354
|
+
throttle: ${throttle},
|
|
355
|
+
}`;
|
|
356
|
+
}
|
|
295
357
|
function createConfigTs(plan) {
|
|
296
358
|
const mongo = plan.capabilities.databases.includes("mongo")
|
|
297
359
|
? "\n mongoUri: readEnv('MONGO_URI', 'mongodb://localhost:27017/app'),"
|
|
@@ -314,10 +376,10 @@ function createConfigTs(plan) {
|
|
|
314
376
|
const sockets = plan.capabilities.realtime.includes("ws")
|
|
315
377
|
? "\n wsPath: readEnv('WS_PATH', '/ws'),\n wsHeartbeatMs: Number(readEnv('WS_HEARTBEAT_MS', '30000')),"
|
|
316
378
|
: "";
|
|
317
|
-
const jwt = plan.capabilities.auth.
|
|
318
|
-
? "\n
|
|
379
|
+
const jwt = plan.capabilities.auth.length > 0
|
|
380
|
+
? "\n jwtAccessSecret: readEnv('JWT_ACCESS_SECRET', 'dev-access-secret'),\n jwtRefreshSecret: readEnv('JWT_REFRESH_SECRET', 'dev-refresh-secret'),"
|
|
319
381
|
: "";
|
|
320
|
-
const apiKey = plan.capabilities.auth.
|
|
382
|
+
const apiKey = plan.capabilities.auth.length > 0
|
|
321
383
|
? "\n apiKeyHeader: readEnv('API_KEY_HEADER', 'x-api-key'),\n devApiKey: readEnv('DEV_API_KEY', 'dev-api-key'),"
|
|
322
384
|
: "";
|
|
323
385
|
return `import 'dotenv/config';
|
|
@@ -343,21 +405,21 @@ export type AppConfig = typeof config;
|
|
|
343
405
|
}
|
|
344
406
|
function createDependenciesTs(plan) {
|
|
345
407
|
const authImport = plan.capabilities.auth.length > 0
|
|
346
|
-
? "import {
|
|
408
|
+
? "import { SoapAuth } from '@soapjs/soap-auth';\nimport { createAuthProvider } from '../features/auth/auth.setup';\n"
|
|
347
409
|
: "";
|
|
348
410
|
const mongoImport = plan.capabilities.databases.includes("mongo")
|
|
349
|
-
? "import { SoapMongo } from '@soapjs/soap-
|
|
411
|
+
? "import { SoapMongo } from '@soapjs/soap-mongo';\nimport { createMongoClient } from '../common/data/mongo/mongo.client';\n"
|
|
350
412
|
: "";
|
|
351
413
|
const sqlDatabases = enabledSqlDatabases(plan.capabilities);
|
|
352
414
|
const sqlImport = sqlDatabases.length > 0
|
|
353
|
-
? `import { SoapSQL } from '@soapjs/soap-
|
|
415
|
+
? `import { SoapSQL } from '@soapjs/soap-sql';
|
|
354
416
|
${sqlDatabases.map((database) => `import { create${(0, naming_1.createNameVariants)(database).pascalName}Client } from '../common/data/${database}/${database}.client';`).join("\n")}
|
|
355
417
|
`
|
|
356
418
|
: "";
|
|
357
419
|
const eventImport = plan.capabilities.messaging.length > 0
|
|
358
420
|
? "import { DomainEventBus } from '@soapjs/soap/cqrs';\nimport { createEventBus } from '../common/events/event-bus.setup';\n"
|
|
359
421
|
: "";
|
|
360
|
-
const
|
|
422
|
+
const authProvider = plan.capabilities.auth.length > 0 ? "await createAuthProvider(_config, logger)" : "undefined";
|
|
361
423
|
const resourceContextType = [
|
|
362
424
|
plan.capabilities.databases.includes("mongo") ? " mongo?: SoapMongo;" : undefined,
|
|
363
425
|
sqlDatabases.length > 0 ? " sql?: Partial<Record<'postgres' | 'mysql' | 'sqlite', SoapSQL>>;" : undefined,
|
|
@@ -377,9 +439,9 @@ ${sqlDatabases.map((database) => {
|
|
|
377
439
|
const eventSetup = plan.capabilities.messaging.length > 0
|
|
378
440
|
? `\n const eventBusRuntime = await createEventBus(_config, logger);\n container.bindValue(DomainEventBus.Token, eventBusRuntime.bus);\n if (eventBusRuntime.drainable) {\n drainables.push(eventBusRuntime.drainable);\n }\n`
|
|
379
441
|
: "";
|
|
442
|
+
const authReturnType = plan.capabilities.auth.length > 0 ? " auth: SoapAuth;" : " auth?: undefined;";
|
|
380
443
|
return `import { ConsoleLogger, DIContainer } from '@soapjs/soap-express';
|
|
381
444
|
import { Drainable } from '@soapjs/soap/events';
|
|
382
|
-
import { AuthStrategy } from '@soapjs/soap/http';
|
|
383
445
|
import { AppConfig } from './config';
|
|
384
446
|
import { registerResources } from './resources';
|
|
385
447
|
${mongoImport}${sqlImport}${eventImport}${authImport}
|
|
@@ -392,13 +454,13 @@ export async function buildContainer(_config: AppConfig): Promise<{
|
|
|
392
454
|
container: DIContainer;
|
|
393
455
|
drainables: Drainable[];
|
|
394
456
|
logger: ConsoleLogger;
|
|
395
|
-
|
|
457
|
+
${authReturnType}
|
|
396
458
|
}> {
|
|
397
459
|
const container = new DIContainer();
|
|
398
460
|
const logger = new ConsoleLogger();
|
|
399
461
|
const drainables: Drainable[] = [];
|
|
400
462
|
const resources: ResourceContext = {};
|
|
401
|
-
const
|
|
463
|
+
const auth = ${authProvider};
|
|
402
464
|
${mongoSetup}${sqlSetup}${eventSetup}
|
|
403
465
|
|
|
404
466
|
await registerResources(container, resources);
|
|
@@ -407,7 +469,7 @@ ${mongoSetup}${sqlSetup}${eventSetup}
|
|
|
407
469
|
container,
|
|
408
470
|
drainables,
|
|
409
471
|
logger,
|
|
410
|
-
|
|
472
|
+
auth,
|
|
411
473
|
};
|
|
412
474
|
}
|
|
413
475
|
`;
|
|
@@ -432,7 +494,6 @@ ${resources.map((resource) => ` await ${resource.functionName}(container, resou
|
|
|
432
494
|
}
|
|
433
495
|
`;
|
|
434
496
|
}
|
|
435
|
-
exports.createResourcesTs = createResourcesTs;
|
|
436
497
|
function createMongoFiles(plan) {
|
|
437
498
|
if (!plan.capabilities.databases.includes("mongo")) {
|
|
438
499
|
return [];
|
|
@@ -441,7 +502,7 @@ function createMongoFiles(plan) {
|
|
|
441
502
|
{
|
|
442
503
|
path: "src/config/mongo.config.ts",
|
|
443
504
|
type: "config",
|
|
444
|
-
content: `import { MongoConfig } from '@soapjs/soap-
|
|
505
|
+
content: `import { MongoConfig } from '@soapjs/soap-mongo';
|
|
445
506
|
import { AppConfig } from './config';
|
|
446
507
|
|
|
447
508
|
export function createMongoConfig(config: Pick<AppConfig, 'mongoUri'>): MongoConfig {
|
|
@@ -462,7 +523,7 @@ export function createMongoConfig(config: Pick<AppConfig, 'mongoUri'>): MongoCon
|
|
|
462
523
|
{
|
|
463
524
|
path: "src/common/data/mongo/mongo.client.ts",
|
|
464
525
|
type: "config",
|
|
465
|
-
content: `import { SoapMongo } from '@soapjs/soap-
|
|
526
|
+
content: `import { SoapMongo } from '@soapjs/soap-mongo';
|
|
466
527
|
import { AppConfig } from '../../../config/config';
|
|
467
528
|
import { createMongoConfig } from '../../../config/mongo.config';
|
|
468
529
|
|
|
@@ -474,7 +535,7 @@ export async function createMongoClient(config: AppConfig): Promise<SoapMongo> {
|
|
|
474
535
|
{
|
|
475
536
|
path: "src/common/data/mongo/mongo.source-factory.ts",
|
|
476
537
|
type: "config",
|
|
477
|
-
content: `import { MongoSource, SoapMongo } from '@soapjs/soap-
|
|
538
|
+
content: `import { MongoSource, SoapMongo } from '@soapjs/soap-mongo';
|
|
478
539
|
import { Document } from 'mongodb';
|
|
479
540
|
|
|
480
541
|
export function createMongoSource<T extends Document = Document>(mongo: SoapMongo, collectionName: string): MongoSource<T> {
|
|
@@ -494,7 +555,7 @@ function createSqlFiles(plan) {
|
|
|
494
555
|
{
|
|
495
556
|
path: "src/common/data/sql/sql.source-factory.ts",
|
|
496
557
|
type: "config",
|
|
497
|
-
content: `import { SoapSQL, SqlDataSource } from '@soapjs/soap-
|
|
558
|
+
content: `import { SoapSQL, SqlDataSource } from '@soapjs/soap-sql';
|
|
498
559
|
|
|
499
560
|
export function createSqlSource<T = Record<string, unknown>>(sql: SoapSQL, tableName: string): SqlDataSource<T> {
|
|
500
561
|
return new SqlDataSource<T>(sql, tableName);
|
|
@@ -515,7 +576,7 @@ function createSqlDatabaseFiles(database) {
|
|
|
515
576
|
{
|
|
516
577
|
path: `src/config/${database}.config.ts`,
|
|
517
578
|
type: "config",
|
|
518
|
-
content: `import { SqlDatabaseConfig } from '@soapjs/soap-
|
|
579
|
+
content: `import { SqlDatabaseConfig } from '@soapjs/soap-sql';
|
|
519
580
|
import { AppConfig } from './config';
|
|
520
581
|
|
|
521
582
|
export function create${names.pascalName}Config(config: Pick<AppConfig, '${configProperty}'>): SqlDatabaseConfig {
|
|
@@ -529,7 +590,7 @@ ${createSqlConfigProperties(database, configProperty)}
|
|
|
529
590
|
{
|
|
530
591
|
path: `src/common/data/${database}/${database}.client.ts`,
|
|
531
592
|
type: "config",
|
|
532
|
-
content: `import { SoapSQL } from '@soapjs/soap-
|
|
593
|
+
content: `import { SoapSQL } from '@soapjs/soap-sql';
|
|
533
594
|
import { AppConfig } from '../../../config/config';
|
|
534
595
|
import { create${names.pascalName}Config } from '../../../config/${database}.config';
|
|
535
596
|
|
|
@@ -543,6 +604,11 @@ export async function create${names.pascalName}Client(config: AppConfig): Promis
|
|
|
543
604
|
function createSqlConfigProperties(database, configProperty) {
|
|
544
605
|
if (database === "sqlite") {
|
|
545
606
|
return ` type: 'sqlite',
|
|
607
|
+
host: 'localhost',
|
|
608
|
+
port: 0,
|
|
609
|
+
database: config.${configProperty}.filename,
|
|
610
|
+
username: '',
|
|
611
|
+
password: '',
|
|
546
612
|
filename: config.${configProperty}.filename,`;
|
|
547
613
|
}
|
|
548
614
|
const sqlType = database === "postgres" ? "postgresql" : "mysql";
|
|
@@ -563,14 +629,6 @@ function createCqrsFiles(plan) {
|
|
|
563
629
|
type: "config",
|
|
564
630
|
content: `// Generated CQRS handler imports. Updated by soap add command/query.
|
|
565
631
|
export {};
|
|
566
|
-
`,
|
|
567
|
-
},
|
|
568
|
-
{
|
|
569
|
-
path: "src/types/soap-express-cqrs.d.ts",
|
|
570
|
-
type: "config",
|
|
571
|
-
content: `declare module '@soapjs/soap-express/cqrs' {
|
|
572
|
-
export * from '@soapjs/soap-express/build/cqrs';
|
|
573
|
-
}
|
|
574
632
|
`,
|
|
575
633
|
},
|
|
576
634
|
];
|
|
@@ -653,7 +711,7 @@ export function createKafkaConfig(config: Pick<AppConfig, 'kafkaBrokers'>) {
|
|
|
653
711
|
}, {
|
|
654
712
|
path: "src/common/events/kafka/kafka.client.ts",
|
|
655
713
|
type: "config",
|
|
656
|
-
content: `import { KafkaEventBus } from '@soapjs/soap-
|
|
714
|
+
content: `import { KafkaEventBus } from '@soapjs/soap-kafka';
|
|
657
715
|
import { AppConfig } from '../../../config/config';
|
|
658
716
|
import { createKafkaConfig } from '../../../config/kafka.config';
|
|
659
717
|
|
|
@@ -664,7 +722,7 @@ export function createKafkaClient(config: AppConfig): KafkaEventBus<Record<strin
|
|
|
664
722
|
}, {
|
|
665
723
|
path: "src/common/events/kafka/kafka-event-bus.ts",
|
|
666
724
|
type: "config",
|
|
667
|
-
content: `import { KafkaDomainEventBus } from '@soapjs/soap-
|
|
725
|
+
content: `import { KafkaDomainEventBus } from '@soapjs/soap-kafka';
|
|
668
726
|
import { Logger } from '@soapjs/soap/common';
|
|
669
727
|
import { AppConfig } from '../../../config/config';
|
|
670
728
|
import { createKafkaClient } from './kafka.client';
|
|
@@ -699,7 +757,7 @@ export const socketHandlers: AppSocketHandler[] = [];
|
|
|
699
757
|
type: "config",
|
|
700
758
|
content: `import { Server } from 'http';
|
|
701
759
|
import { Drainable } from '@soapjs/soap/events';
|
|
702
|
-
import { SocketMessage, SocketServer, WebSocketServerAdapter } from '@soapjs/soap-
|
|
760
|
+
import { SocketMessage, SocketServer, WebSocketServerAdapter } from '@soapjs/soap-socket';
|
|
703
761
|
import { AppConfig } from '../../config/config';
|
|
704
762
|
import { socketHandlers } from '../../config/sockets';
|
|
705
763
|
|
|
@@ -763,17 +821,9 @@ ${names}
|
|
|
763
821
|
];
|
|
764
822
|
`;
|
|
765
823
|
}
|
|
766
|
-
exports.createControllersTs = createControllersTs;
|
|
767
824
|
function createInitialControllers(plan) {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
}
|
|
771
|
-
return [
|
|
772
|
-
{
|
|
773
|
-
className: "AuthController",
|
|
774
|
-
importPath: "../features/auth/api/auth.controller",
|
|
775
|
-
},
|
|
776
|
-
];
|
|
825
|
+
void plan;
|
|
826
|
+
return [];
|
|
777
827
|
}
|
|
778
828
|
function createEnvExample(plan) {
|
|
779
829
|
const lines = ["NODE_ENV=development", "PORT=3000", "LOG_LEVEL=debug"];
|
|
@@ -793,7 +843,7 @@ function createEnvExample(plan) {
|
|
|
793
843
|
lines.push("REDIS_URL=redis://localhost:6379");
|
|
794
844
|
}
|
|
795
845
|
if (plan.capabilities.auth.includes("jwt") || plan.capabilities.auth.includes("local")) {
|
|
796
|
-
lines.push("
|
|
846
|
+
lines.push("JWT_ACCESS_SECRET=change-me-access", "JWT_REFRESH_SECRET=change-me-refresh");
|
|
797
847
|
}
|
|
798
848
|
if (plan.capabilities.auth.includes("api-key")) {
|
|
799
849
|
lines.push("API_KEY_HEADER=x-api-key", "DEV_API_KEY=dev-api-key");
|
|
@@ -847,6 +897,7 @@ curl http://localhost:3000/openapi.json
|
|
|
847
897
|
`
|
|
848
898
|
: "";
|
|
849
899
|
const authSection = createReadmeAuthSection(plan);
|
|
900
|
+
const monitoringSection = createReadmeMonitoringSection(plan);
|
|
850
901
|
const addCommands = `## Add More Code
|
|
851
902
|
|
|
852
903
|
\`\`\`bash
|
|
@@ -860,6 +911,8 @@ soap update config --add-api-client bruno
|
|
|
860
911
|
|
|
861
912
|
Generated SoapJS service.
|
|
862
913
|
|
|
914
|
+
Requires Node.js 24.17.0 or newer. The generated runtime uses SoapJS 0.14, soap-auth 1.x, and the soap-express security/auth helpers.
|
|
915
|
+
|
|
863
916
|
## Capabilities
|
|
864
917
|
|
|
865
918
|
- Framework: ${plan.framework}
|
|
@@ -892,7 +945,7 @@ Build:
|
|
|
892
945
|
${buildCommand}
|
|
893
946
|
\`\`\`
|
|
894
947
|
|
|
895
|
-
${dockerSection}${brunoSection}${openApiSection}${authSection}## Folder Structure
|
|
948
|
+
${dockerSection}${brunoSection}${openApiSection}${authSection}${monitoringSection}## Folder Structure
|
|
896
949
|
|
|
897
950
|
\`\`\`txt
|
|
898
951
|
src/
|
|
@@ -915,6 +968,25 @@ src/
|
|
|
915
968
|
Generated metadata is stored in \`.soap/\`.
|
|
916
969
|
|
|
917
970
|
${addCommands}
|
|
971
|
+
`;
|
|
972
|
+
}
|
|
973
|
+
function createReadmeMonitoringSection(plan) {
|
|
974
|
+
const endpoints = [];
|
|
975
|
+
if (plan.capabilities.telemetry.includes("metrics")) {
|
|
976
|
+
endpoints.push("- Metrics: http://localhost:3000/metrics");
|
|
977
|
+
}
|
|
978
|
+
if (plan.capabilities.telemetry.includes("memory")) {
|
|
979
|
+
endpoints.push("- Memory monitoring: http://localhost:3000/memory");
|
|
980
|
+
}
|
|
981
|
+
if (endpoints.length === 0) {
|
|
982
|
+
return "";
|
|
983
|
+
}
|
|
984
|
+
return `## Monitoring
|
|
985
|
+
|
|
986
|
+
Monitoring endpoints are opt-in and were generated because telemetry includes \`${plan.capabilities.telemetry.filter((item) => item === "metrics" || item === "memory").join(", ")}\`.
|
|
987
|
+
|
|
988
|
+
${endpoints.join("\n")}
|
|
989
|
+
|
|
918
990
|
`;
|
|
919
991
|
}
|
|
920
992
|
function createReadmeAuthSection(plan) {
|
|
@@ -927,7 +999,7 @@ Default Bruno login variables:
|
|
|
927
999
|
- email: \`admin@example.com\`
|
|
928
1000
|
- password: \`admin123\`
|
|
929
1001
|
|
|
930
|
-
Set \`
|
|
1002
|
+
Auth is registered through \`SoapAuth.create(...)\` and \`createAuthRouter(...)\`. Set \`JWT_ACCESS_SECRET\` and \`JWT_REFRESH_SECRET\` in \`.env.example\` or your local \`.env\`.
|
|
931
1003
|
|
|
932
1004
|
`);
|
|
933
1005
|
}
|
|
@@ -939,12 +1011,17 @@ Default header and key:
|
|
|
939
1011
|
- \`API_KEY_HEADER=x-api-key\`
|
|
940
1012
|
- \`DEV_API_KEY=dev-api-key\`
|
|
941
1013
|
|
|
1014
|
+
API key auth uses \`createApiKeyAuthConfig(...)\` with a development \`retrieveUserByApiKey\` implementation.
|
|
1015
|
+
|
|
942
1016
|
`);
|
|
943
1017
|
}
|
|
944
|
-
|
|
1018
|
+
const security = sections.length > 0
|
|
1019
|
+
? `Route-specific throttling is enabled for \`POST /auth/login\`, \`POST /auth/refresh\`, and OAuth callbacks through soap-express security config.\n\n`
|
|
1020
|
+
: "";
|
|
1021
|
+
return sections.length > 0 ? `## Auth\n\n${security}${sections.join("\n")}` : "";
|
|
945
1022
|
}
|
|
946
1023
|
function createDockerfile() {
|
|
947
|
-
return `FROM node:
|
|
1024
|
+
return `FROM node:24-alpine
|
|
948
1025
|
WORKDIR /app
|
|
949
1026
|
COPY package*.json ./
|
|
950
1027
|
RUN npm install
|
|
@@ -1039,16 +1116,24 @@ function createAuthFiles(plan) {
|
|
|
1039
1116
|
path: "src/features/auth/domain/auth-user.ts",
|
|
1040
1117
|
type: "config",
|
|
1041
1118
|
owner: "auth",
|
|
1042
|
-
content: `
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
name
|
|
1046
|
-
|
|
1119
|
+
content: `import { AuthUser as SoapAuthUser } from '@soapjs/soap/http';
|
|
1120
|
+
|
|
1121
|
+
export interface AuthUser extends SoapAuthUser {
|
|
1122
|
+
name?: string;
|
|
1123
|
+
}
|
|
1124
|
+
`,
|
|
1125
|
+
},
|
|
1126
|
+
{
|
|
1127
|
+
path: "src/types/soap-express-auth.d.ts",
|
|
1128
|
+
type: "config",
|
|
1129
|
+
owner: "auth",
|
|
1130
|
+
content: `declare module '@soapjs/soap-express/auth' {
|
|
1131
|
+
export * from '@soapjs/soap-express/build/auth';
|
|
1047
1132
|
}
|
|
1048
1133
|
`,
|
|
1049
1134
|
},
|
|
1050
1135
|
];
|
|
1051
|
-
if (usesJwtAuth(plan)) {
|
|
1136
|
+
if (usesJwtAuth(plan) || plan.capabilities.auth.includes("api-key")) {
|
|
1052
1137
|
files.push({
|
|
1053
1138
|
path: "src/features/auth/data/dev-users.ts",
|
|
1054
1139
|
type: "config",
|
|
@@ -1072,151 +1157,6 @@ export const devUsers: DevUser[] = [
|
|
|
1072
1157
|
export function findDevUser(email: string): DevUser | undefined {
|
|
1073
1158
|
return devUsers.find((user) => user.email === email);
|
|
1074
1159
|
}
|
|
1075
|
-
`,
|
|
1076
|
-
}, {
|
|
1077
|
-
path: "src/features/auth/auth.tokens.ts",
|
|
1078
|
-
type: "config",
|
|
1079
|
-
owner: "auth",
|
|
1080
|
-
content: `import jwt from 'jsonwebtoken';
|
|
1081
|
-
import { AuthUser } from './domain/auth-user';
|
|
1082
|
-
|
|
1083
|
-
export interface AuthTokenPayload {
|
|
1084
|
-
sub: string;
|
|
1085
|
-
email: string;
|
|
1086
|
-
name: string;
|
|
1087
|
-
roles: string[];
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
export function signAccessToken(user: AuthUser, secret: string): string {
|
|
1091
|
-
return jwt.sign(
|
|
1092
|
-
{
|
|
1093
|
-
sub: user.id,
|
|
1094
|
-
email: user.email,
|
|
1095
|
-
name: user.name,
|
|
1096
|
-
roles: user.roles,
|
|
1097
|
-
},
|
|
1098
|
-
secret,
|
|
1099
|
-
{ expiresIn: '1h' }
|
|
1100
|
-
);
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
export function verifyAccessToken(token: string, secret: string): AuthUser {
|
|
1104
|
-
const payload = jwt.verify(token, secret) as AuthTokenPayload;
|
|
1105
|
-
|
|
1106
|
-
return {
|
|
1107
|
-
id: payload.sub,
|
|
1108
|
-
email: payload.email,
|
|
1109
|
-
name: payload.name,
|
|
1110
|
-
roles: payload.roles ?? [],
|
|
1111
|
-
};
|
|
1112
|
-
}
|
|
1113
|
-
`,
|
|
1114
|
-
}, {
|
|
1115
|
-
path: "src/features/auth/jwt.strategy.ts",
|
|
1116
|
-
type: "config",
|
|
1117
|
-
owner: "auth",
|
|
1118
|
-
content: `import { AuthStrategy, HttpContext } from '@soapjs/soap/http';
|
|
1119
|
-
import { verifyAccessToken } from './auth.tokens';
|
|
1120
|
-
import { AuthUser } from './domain/auth-user';
|
|
1121
|
-
|
|
1122
|
-
export class JwtAuthStrategy implements AuthStrategy<AuthUser> {
|
|
1123
|
-
readonly name = 'jwt';
|
|
1124
|
-
|
|
1125
|
-
constructor(private readonly secret: string) {}
|
|
1126
|
-
|
|
1127
|
-
async authenticate(ctx: HttpContext) {
|
|
1128
|
-
const authorization = ctx.req.headers.authorization;
|
|
1129
|
-
const header = Array.isArray(authorization) ? authorization[0] : authorization;
|
|
1130
|
-
|
|
1131
|
-
if (!header?.startsWith('Bearer ')) {
|
|
1132
|
-
return null;
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
const token = header.slice('Bearer '.length);
|
|
1136
|
-
const user = verifyAccessToken(token, this.secret);
|
|
1137
|
-
|
|
1138
|
-
return { user };
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
`,
|
|
1142
|
-
}, {
|
|
1143
|
-
path: "src/features/auth/api/auth.controller.ts",
|
|
1144
|
-
type: "config",
|
|
1145
|
-
owner: "auth",
|
|
1146
|
-
content: `import { Request } from 'express';
|
|
1147
|
-
import { Auth, Controller, Get, Post } from '@soapjs/soap-express';
|
|
1148
|
-
import { config } from '../../../config/config';
|
|
1149
|
-
import { signAccessToken } from '../auth.tokens';
|
|
1150
|
-
import { findDevUser } from '../data/dev-users';
|
|
1151
|
-
|
|
1152
|
-
@Controller('/auth', {
|
|
1153
|
-
apiDoc: {
|
|
1154
|
-
tags: ['Auth'],
|
|
1155
|
-
description: 'Development auth endpoints generated by SoapJS CLI',
|
|
1156
|
-
},
|
|
1157
|
-
})
|
|
1158
|
-
export class AuthController {
|
|
1159
|
-
@Post('/login')
|
|
1160
|
-
async login(req: Request): Promise<unknown> {
|
|
1161
|
-
const { email, password } = req.body ?? {};
|
|
1162
|
-
const user = typeof email === 'string' ? findDevUser(email) : undefined;
|
|
1163
|
-
|
|
1164
|
-
if (!user || user.password !== password) {
|
|
1165
|
-
return { error: 'Invalid credentials' };
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
const { password: _password, ...safeUser } = user;
|
|
1169
|
-
const accessToken = signAccessToken(safeUser, config.jwtSecret);
|
|
1170
|
-
|
|
1171
|
-
return {
|
|
1172
|
-
accessToken,
|
|
1173
|
-
user: safeUser,
|
|
1174
|
-
};
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
@Get('/me')
|
|
1178
|
-
@Auth('jwt')
|
|
1179
|
-
async me(req: Request): Promise<unknown> {
|
|
1180
|
-
return (req as Request & { user?: unknown }).user;
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
`,
|
|
1184
|
-
});
|
|
1185
|
-
}
|
|
1186
|
-
if (plan.capabilities.auth.includes("api-key")) {
|
|
1187
|
-
files.push({
|
|
1188
|
-
path: "src/features/auth/api-key.strategy.ts",
|
|
1189
|
-
type: "config",
|
|
1190
|
-
owner: "auth",
|
|
1191
|
-
content: `import { AuthStrategy, HttpContext } from '@soapjs/soap/http';
|
|
1192
|
-
import { AuthUser } from './domain/auth-user';
|
|
1193
|
-
|
|
1194
|
-
export class ApiKeyAuthStrategy implements AuthStrategy<AuthUser> {
|
|
1195
|
-
readonly name = 'api-key';
|
|
1196
|
-
|
|
1197
|
-
constructor(
|
|
1198
|
-
private readonly headerName: string,
|
|
1199
|
-
private readonly expectedApiKey: string
|
|
1200
|
-
) {}
|
|
1201
|
-
|
|
1202
|
-
async authenticate(ctx: HttpContext) {
|
|
1203
|
-
const value = ctx.req.headers[this.headerName.toLowerCase()];
|
|
1204
|
-
const apiKey = Array.isArray(value) ? value[0] : value;
|
|
1205
|
-
|
|
1206
|
-
if (!apiKey || apiKey !== this.expectedApiKey) {
|
|
1207
|
-
return null;
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
return {
|
|
1211
|
-
user: {
|
|
1212
|
-
id: 'api-key-client',
|
|
1213
|
-
email: 'api-key@example.com',
|
|
1214
|
-
name: 'API Key Client',
|
|
1215
|
-
roles: ['admin'],
|
|
1216
|
-
},
|
|
1217
|
-
};
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
1160
|
`,
|
|
1221
1161
|
});
|
|
1222
1162
|
}
|
|
@@ -1238,34 +1178,139 @@ function usesJwtAuth(plan) {
|
|
|
1238
1178
|
}
|
|
1239
1179
|
function createAuthSetupTs(plan) {
|
|
1240
1180
|
const imports = [
|
|
1241
|
-
"import {
|
|
1181
|
+
"import { SoapAuth } from '@soapjs/soap-auth';",
|
|
1182
|
+
"import { createApiKeyAuthConfig, createJwtAuthConfig, createLocalAuthConfig } from '@soapjs/soap-auth/recipes';",
|
|
1183
|
+
"import { Logger } from '@soapjs/soap/common';",
|
|
1242
1184
|
"import { AppConfig } from '../../config/config';",
|
|
1185
|
+
"import { AuthUser } from './domain/auth-user';",
|
|
1186
|
+
"import { devUsers, findDevUser } from './data/dev-users';",
|
|
1243
1187
|
];
|
|
1244
|
-
const
|
|
1245
|
-
if (
|
|
1246
|
-
|
|
1247
|
-
|
|
1188
|
+
const httpConfigs = [];
|
|
1189
|
+
if (plan.capabilities.auth.includes("jwt")) {
|
|
1190
|
+
httpConfigs.push(` jwt: createJwtConfig(config),`);
|
|
1191
|
+
}
|
|
1192
|
+
if (plan.capabilities.auth.includes("local") || plan.capabilities.auth.includes("jwt")) {
|
|
1193
|
+
httpConfigs.push(` local: createLocalConfig(config),`);
|
|
1248
1194
|
}
|
|
1249
1195
|
if (plan.capabilities.auth.includes("api-key")) {
|
|
1250
|
-
|
|
1251
|
-
body.push("", " strategies.push(new ApiKeyAuthStrategy(config.apiKeyHeader, config.devApiKey));");
|
|
1196
|
+
httpConfigs.push(` apiKey: createApiKeyConfig(config),`);
|
|
1252
1197
|
}
|
|
1253
|
-
body.push("", " return strategies;");
|
|
1254
1198
|
return `${imports.join("\n")}
|
|
1255
1199
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1200
|
+
interface DevLoginPayload {
|
|
1201
|
+
identifier?: string;
|
|
1202
|
+
email?: string;
|
|
1203
|
+
password?: string;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function safeUser(user: AuthUser & { password?: string }): AuthUser {
|
|
1207
|
+
return {
|
|
1208
|
+
id: user.id,
|
|
1209
|
+
email: user.email,
|
|
1210
|
+
name: user.name,
|
|
1211
|
+
roles: user.roles,
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
async function fetchUser(payload: unknown): Promise<AuthUser | null> {
|
|
1216
|
+
const id = typeof payload === 'object' && payload !== null && 'id' in payload ? String((payload as { id: unknown }).id) : undefined;
|
|
1217
|
+
const email = typeof payload === 'string'
|
|
1218
|
+
? payload
|
|
1219
|
+
: typeof payload === 'object' && payload !== null && 'email' in payload
|
|
1220
|
+
? String((payload as { email: unknown }).email)
|
|
1221
|
+
: undefined;
|
|
1222
|
+
const user = id
|
|
1223
|
+
? devUsers.find((candidate) => candidate.id === id)
|
|
1224
|
+
: email
|
|
1225
|
+
? findDevUser(email)
|
|
1226
|
+
: undefined;
|
|
1227
|
+
|
|
1228
|
+
return user ? safeUser(user) : null;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
function createJwtConfig(config: AppConfig) {
|
|
1232
|
+
return createJwtAuthConfig({
|
|
1233
|
+
accessSecret: config.jwtAccessSecret,
|
|
1234
|
+
refreshSecret: config.jwtRefreshSecret,
|
|
1235
|
+
user: { fetchUser },
|
|
1236
|
+
accessToken: {
|
|
1237
|
+
options: { expiresIn: '15m' },
|
|
1238
|
+
buildPayload: (user: AuthUser) => ({ id: user.id, email: user.email, roles: user.roles }),
|
|
1239
|
+
},
|
|
1240
|
+
refreshToken: {
|
|
1241
|
+
options: { expiresIn: '7d' },
|
|
1242
|
+
buildPayload: (user: AuthUser) => ({ id: user.id }),
|
|
1243
|
+
},
|
|
1244
|
+
routes: {
|
|
1245
|
+
login: { path: '/auth/login' },
|
|
1246
|
+
refresh: { path: '/auth/refresh' },
|
|
1247
|
+
logout: { path: '/auth/logout' },
|
|
1248
|
+
},
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function createLocalConfig(_config: AppConfig) {
|
|
1253
|
+
return createLocalAuthConfig({
|
|
1254
|
+
basePath: '/auth',
|
|
1255
|
+
credentials: {
|
|
1256
|
+
extractCredentials: <TCredentials>(context: unknown): TCredentials => {
|
|
1257
|
+
const authContext = context as { body?: DevLoginPayload; req?: { body?: DevLoginPayload } };
|
|
1258
|
+
const body = authContext.body ?? authContext.req?.body ?? {};
|
|
1259
|
+
return {
|
|
1260
|
+
identifier: body.identifier ?? body.email ?? '',
|
|
1261
|
+
password: body.password ?? '',
|
|
1262
|
+
} as TCredentials;
|
|
1263
|
+
},
|
|
1264
|
+
verifyCredentials: async (identifier: string, password: string) => {
|
|
1265
|
+
const user = findDevUser(identifier);
|
|
1266
|
+
return user?.password === password;
|
|
1267
|
+
},
|
|
1268
|
+
},
|
|
1269
|
+
user: { fetchUser },
|
|
1270
|
+
routes: {
|
|
1271
|
+
login: { path: '/auth/login' },
|
|
1272
|
+
logout: { path: '/auth/logout' },
|
|
1273
|
+
},
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
function createApiKeyConfig(config: AppConfig) {
|
|
1278
|
+
return createApiKeyAuthConfig({
|
|
1279
|
+
keyType: 'long-term',
|
|
1280
|
+
extractApiKey: (context: unknown) => {
|
|
1281
|
+
const authContext = context as { req?: { headers?: Record<string, string | string[] | undefined> }; headers?: Record<string, string | string[] | undefined> };
|
|
1282
|
+
const headers = authContext.req?.headers ?? authContext.headers ?? {};
|
|
1283
|
+
const value = headers[config.apiKeyHeader.toLowerCase()];
|
|
1284
|
+
return Array.isArray(value) ? value[0] ?? null : value ?? null;
|
|
1285
|
+
},
|
|
1286
|
+
retrieveUserByApiKey: async (apiKey: string): Promise<AuthUser | null> => {
|
|
1287
|
+
if (apiKey !== config.devApiKey) {
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
return {
|
|
1292
|
+
id: 'api-key-client',
|
|
1293
|
+
email: 'api-key@example.com',
|
|
1294
|
+
name: 'API Key Client',
|
|
1295
|
+
roles: ['admin'],
|
|
1296
|
+
};
|
|
1297
|
+
},
|
|
1298
|
+
trackApiKeyUsage: async (_apiKey: string) => undefined,
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
export async function createAuthProvider(config: AppConfig, logger?: Logger): Promise<SoapAuth> {
|
|
1303
|
+
return SoapAuth.create({
|
|
1304
|
+
logger,
|
|
1305
|
+
http: {
|
|
1306
|
+
${httpConfigs.join("\n")}
|
|
1307
|
+
},
|
|
1308
|
+
});
|
|
1258
1309
|
}
|
|
1259
1310
|
`;
|
|
1260
1311
|
}
|
|
1261
1312
|
function createAuthIndexTs(plan) {
|
|
1262
1313
|
const exports = ["export * from './auth.setup';", "export * from './domain/auth-user';"];
|
|
1263
|
-
if (usesJwtAuth(plan)) {
|
|
1264
|
-
exports.push("export * from './api/auth.controller';", "export * from './auth.tokens';", "export * from './jwt.strategy';");
|
|
1265
|
-
}
|
|
1266
|
-
if (plan.capabilities.auth.includes("api-key")) {
|
|
1267
|
-
exports.push("export * from './api-key.strategy';");
|
|
1268
|
-
}
|
|
1269
1314
|
return `${exports.join("\n")}\n`;
|
|
1270
1315
|
}
|
|
1271
1316
|
function createFeaturesIndexTs(plan) {
|