@objectstack/plugin-auth 3.2.6 → 3.2.8

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/dist/index.js CHANGED
@@ -399,10 +399,28 @@ var AuthManager = class {
399
399
  verification: {
400
400
  ...AUTH_VERIFICATION_CONFIG
401
401
  },
402
- // Email configuration
402
+ // Social / OAuth providers
403
+ ...this.config.socialProviders ? { socialProviders: this.config.socialProviders } : {},
404
+ // Email and password configuration
403
405
  emailAndPassword: {
404
- enabled: true
406
+ enabled: this.config.emailAndPassword?.enabled ?? true,
407
+ ...this.config.emailAndPassword?.disableSignUp != null ? { disableSignUp: this.config.emailAndPassword.disableSignUp } : {},
408
+ ...this.config.emailAndPassword?.requireEmailVerification != null ? { requireEmailVerification: this.config.emailAndPassword.requireEmailVerification } : {},
409
+ ...this.config.emailAndPassword?.minPasswordLength != null ? { minPasswordLength: this.config.emailAndPassword.minPasswordLength } : {},
410
+ ...this.config.emailAndPassword?.maxPasswordLength != null ? { maxPasswordLength: this.config.emailAndPassword.maxPasswordLength } : {},
411
+ ...this.config.emailAndPassword?.resetPasswordTokenExpiresIn != null ? { resetPasswordTokenExpiresIn: this.config.emailAndPassword.resetPasswordTokenExpiresIn } : {},
412
+ ...this.config.emailAndPassword?.autoSignIn != null ? { autoSignIn: this.config.emailAndPassword.autoSignIn } : {},
413
+ ...this.config.emailAndPassword?.revokeSessionsOnPasswordReset != null ? { revokeSessionsOnPasswordReset: this.config.emailAndPassword.revokeSessionsOnPasswordReset } : {}
405
414
  },
415
+ // Email verification
416
+ ...this.config.emailVerification ? {
417
+ emailVerification: {
418
+ ...this.config.emailVerification.sendOnSignUp != null ? { sendOnSignUp: this.config.emailVerification.sendOnSignUp } : {},
419
+ ...this.config.emailVerification.sendOnSignIn != null ? { sendOnSignIn: this.config.emailVerification.sendOnSignIn } : {},
420
+ ...this.config.emailVerification.autoSignInAfterVerification != null ? { autoSignInAfterVerification: this.config.emailVerification.autoSignInAfterVerification } : {},
421
+ ...this.config.emailVerification.expiresIn != null ? { expiresIn: this.config.emailVerification.expiresIn } : {}
422
+ }
423
+ } : {},
406
424
  // Session configuration
407
425
  session: {
408
426
  ...AUTH_SESSION_CONFIG,
@@ -412,7 +430,18 @@ var AuthManager = class {
412
430
  // 1 day default
413
431
  },
414
432
  // better-auth plugins — registered based on AuthPluginConfig flags
415
- plugins: this.buildPluginList()
433
+ plugins: this.buildPluginList(),
434
+ // Trusted origins for CSRF protection (supports wildcards like "https://*.example.com")
435
+ ...this.config.trustedOrigins?.length ? { trustedOrigins: this.config.trustedOrigins } : {},
436
+ // Advanced options (cross-subdomain cookies, secure cookies, CSRF, etc.)
437
+ ...this.config.advanced ? {
438
+ advanced: {
439
+ ...this.config.advanced.crossSubDomainCookies ? { crossSubDomainCookies: this.config.advanced.crossSubDomainCookies } : {},
440
+ ...this.config.advanced.useSecureCookies != null ? { useSecureCookies: this.config.advanced.useSecureCookies } : {},
441
+ ...this.config.advanced.disableCSRFCheck != null ? { disableCSRFCheck: this.config.advanced.disableCSRFCheck } : {},
442
+ ...this.config.advanced.cookiePrefix != null ? { cookiePrefix: this.config.advanced.cookiePrefix } : {}
443
+ }
444
+ } : {}
416
445
  };
417
446
  return (0, import_better_auth.betterAuth)(betterAuthConfig);
418
447
  }
@@ -482,6 +511,25 @@ var AuthManager = class {
482
511
  }
483
512
  return envSecret;
484
513
  }
514
+ /**
515
+ * Update the base URL at runtime.
516
+ *
517
+ * This **must** be called before the first request triggers lazy
518
+ * initialisation of the better-auth instance — typically from a
519
+ * `kernel:ready` hook where the actual server port is known.
520
+ *
521
+ * If the auth instance has already been created this is a no-op and
522
+ * a warning is emitted.
523
+ */
524
+ setRuntimeBaseUrl(url) {
525
+ if (this.auth) {
526
+ console.warn(
527
+ "[AuthManager] setRuntimeBaseUrl() called after the auth instance was already created \u2014 ignoring. Ensure this method is called before the first request."
528
+ );
529
+ return;
530
+ }
531
+ this.config = { ...this.config, baseUrl: url };
532
+ }
485
533
  /**
486
534
  * Get the underlying better-auth instance
487
535
  * Useful for advanced use cases
@@ -522,130 +570,6 @@ var AuthManager = class {
522
570
  }
523
571
  };
524
572
 
525
- // src/auth-plugin.ts
526
- var AuthPlugin = class {
527
- constructor(options = {}) {
528
- this.name = "com.objectstack.auth";
529
- this.type = "standard";
530
- this.version = "1.0.0";
531
- this.dependencies = [];
532
- this.authManager = null;
533
- this.options = {
534
- registerRoutes: true,
535
- basePath: "/api/v1/auth",
536
- ...options
537
- };
538
- }
539
- async init(ctx) {
540
- ctx.logger.info("Initializing Auth Plugin...");
541
- if (!this.options.secret) {
542
- throw new Error("AuthPlugin: secret is required");
543
- }
544
- const dataEngine = ctx.getService("data");
545
- if (!dataEngine) {
546
- ctx.logger.warn("No data engine service found - auth will use in-memory storage");
547
- }
548
- this.authManager = new AuthManager({
549
- ...this.options,
550
- dataEngine
551
- });
552
- ctx.registerService("auth", this.authManager);
553
- ctx.logger.info("Auth Plugin initialized successfully");
554
- }
555
- async start(ctx) {
556
- ctx.logger.info("Starting Auth Plugin...");
557
- if (!this.authManager) {
558
- throw new Error("Auth manager not initialized");
559
- }
560
- if (this.options.registerRoutes) {
561
- ctx.hook("kernel:ready", async () => {
562
- let httpServer = null;
563
- try {
564
- httpServer = ctx.getService("http-server");
565
- } catch {
566
- }
567
- if (httpServer) {
568
- this.registerAuthRoutes(httpServer, ctx);
569
- ctx.logger.info(`Auth routes registered at ${this.options.basePath}`);
570
- } else {
571
- ctx.logger.warn(
572
- "No HTTP server available \u2014 auth routes not registered. Auth service is still available for MSW/mock environments via HttpDispatcher."
573
- );
574
- }
575
- });
576
- }
577
- try {
578
- const ql = ctx.getService("objectql");
579
- if (ql && typeof ql.registerMiddleware === "function") {
580
- ql.registerMiddleware(async (opCtx, next) => {
581
- if (opCtx.context?.userId || opCtx.context?.isSystem) {
582
- return next();
583
- }
584
- await next();
585
- });
586
- ctx.logger.info("Auth middleware registered on ObjectQL engine");
587
- }
588
- } catch (_e) {
589
- ctx.logger.debug("ObjectQL engine not available, skipping auth middleware registration");
590
- }
591
- ctx.logger.info("Auth Plugin started successfully");
592
- }
593
- async destroy() {
594
- this.authManager = null;
595
- }
596
- /**
597
- * Register authentication routes with HTTP server
598
- *
599
- * Uses better-auth's universal handler for all authentication requests.
600
- * This forwards all requests under basePath to better-auth, which handles:
601
- * - Email/password authentication
602
- * - OAuth providers (Google, GitHub, etc.)
603
- * - Session management
604
- * - Password reset
605
- * - Email verification
606
- * - 2FA, passkeys, magic links (if enabled)
607
- */
608
- registerAuthRoutes(httpServer, ctx) {
609
- if (!this.authManager) return;
610
- const basePath = this.options.basePath || "/api/v1/auth";
611
- if (!("getRawApp" in httpServer) || typeof httpServer.getRawApp !== "function") {
612
- ctx.logger.error("HTTP server does not support getRawApp() - wildcard routing requires Hono server");
613
- throw new Error(
614
- "AuthPlugin requires HonoServerPlugin for wildcard routing support. Please ensure HonoServerPlugin is loaded before AuthPlugin."
615
- );
616
- }
617
- const rawApp = httpServer.getRawApp();
618
- rawApp.all(`${basePath}/*`, async (c) => {
619
- try {
620
- const response = await this.authManager.handleRequest(c.req.raw);
621
- if (response.status >= 500) {
622
- try {
623
- const body = await response.clone().text();
624
- ctx.logger.error("[AuthPlugin] better-auth returned server error", new Error(`HTTP ${response.status}: ${body}`));
625
- } catch {
626
- ctx.logger.error("[AuthPlugin] better-auth returned server error", new Error(`HTTP ${response.status}: (unable to read body)`));
627
- }
628
- }
629
- return response;
630
- } catch (error) {
631
- const err = error instanceof Error ? error : new Error(String(error));
632
- ctx.logger.error("Auth request error:", err);
633
- return new Response(
634
- JSON.stringify({
635
- success: false,
636
- error: err.message
637
- }),
638
- {
639
- status: 500,
640
- headers: { "Content-Type": "application/json" }
641
- }
642
- );
643
- }
644
- });
645
- ctx.logger.info(`Auth routes registered: All requests under ${basePath}/* forwarded to better-auth`);
646
- }
647
- };
648
-
649
573
  // src/objects/sys-user.object.ts
650
574
  var import_data = require("@objectstack/spec/data");
651
575
  var SysUser = import_data.ObjectSchema.create({
@@ -1355,6 +1279,165 @@ var SysTwoFactor = import_data11.ObjectSchema.create({
1355
1279
  mru: false
1356
1280
  }
1357
1281
  });
1282
+
1283
+ // src/auth-plugin.ts
1284
+ var AuthPlugin = class {
1285
+ constructor(options = {}) {
1286
+ this.name = "com.objectstack.auth";
1287
+ this.type = "standard";
1288
+ this.version = "1.0.0";
1289
+ this.dependencies = [];
1290
+ this.authManager = null;
1291
+ this.options = {
1292
+ registerRoutes: true,
1293
+ basePath: "/api/v1/auth",
1294
+ ...options
1295
+ };
1296
+ }
1297
+ async init(ctx) {
1298
+ ctx.logger.info("Initializing Auth Plugin...");
1299
+ if (!this.options.secret) {
1300
+ throw new Error("AuthPlugin: secret is required");
1301
+ }
1302
+ const dataEngine = ctx.getService("data");
1303
+ if (!dataEngine) {
1304
+ ctx.logger.warn("No data engine service found - auth will use in-memory storage");
1305
+ }
1306
+ this.authManager = new AuthManager({
1307
+ ...this.options,
1308
+ dataEngine
1309
+ });
1310
+ ctx.registerService("auth", this.authManager);
1311
+ ctx.registerService("app.com.objectstack.system", {
1312
+ id: "com.objectstack.system",
1313
+ name: "System",
1314
+ version: "1.0.0",
1315
+ type: "plugin",
1316
+ namespace: "sys",
1317
+ objects: [
1318
+ SysUser,
1319
+ SysSession,
1320
+ SysAccount,
1321
+ SysVerification,
1322
+ SysOrganization,
1323
+ SysMember,
1324
+ SysInvitation,
1325
+ SysTeam,
1326
+ SysTeamMember,
1327
+ SysApiKey,
1328
+ SysTwoFactor
1329
+ ]
1330
+ });
1331
+ ctx.logger.info("Auth Plugin initialized successfully");
1332
+ }
1333
+ async start(ctx) {
1334
+ ctx.logger.info("Starting Auth Plugin...");
1335
+ if (!this.authManager) {
1336
+ throw new Error("Auth manager not initialized");
1337
+ }
1338
+ if (this.options.registerRoutes) {
1339
+ ctx.hook("kernel:ready", async () => {
1340
+ let httpServer = null;
1341
+ try {
1342
+ httpServer = ctx.getService("http-server");
1343
+ } catch {
1344
+ }
1345
+ if (httpServer) {
1346
+ const serverWithPort = httpServer;
1347
+ if (this.authManager && typeof serverWithPort.getPort === "function") {
1348
+ const actualPort = serverWithPort.getPort();
1349
+ if (actualPort) {
1350
+ const configuredUrl = this.options.baseUrl || "http://localhost:3000";
1351
+ const configuredOrigin = new URL(configuredUrl).origin;
1352
+ const actualUrl = `http://localhost:${actualPort}`;
1353
+ if (configuredOrigin !== actualUrl) {
1354
+ this.authManager.setRuntimeBaseUrl(actualUrl);
1355
+ ctx.logger.info(
1356
+ `Auth baseUrl auto-updated to ${actualUrl} (configured: ${configuredUrl})`
1357
+ );
1358
+ }
1359
+ }
1360
+ }
1361
+ this.registerAuthRoutes(httpServer, ctx);
1362
+ ctx.logger.info(`Auth routes registered at ${this.options.basePath}`);
1363
+ } else {
1364
+ ctx.logger.warn(
1365
+ "No HTTP server available \u2014 auth routes not registered. Auth service is still available for MSW/mock environments via HttpDispatcher."
1366
+ );
1367
+ }
1368
+ });
1369
+ }
1370
+ try {
1371
+ const ql = ctx.getService("objectql");
1372
+ if (ql && typeof ql.registerMiddleware === "function") {
1373
+ ql.registerMiddleware(async (opCtx, next) => {
1374
+ if (opCtx.context?.userId || opCtx.context?.isSystem) {
1375
+ return next();
1376
+ }
1377
+ await next();
1378
+ });
1379
+ ctx.logger.info("Auth middleware registered on ObjectQL engine");
1380
+ }
1381
+ } catch (_e) {
1382
+ ctx.logger.debug("ObjectQL engine not available, skipping auth middleware registration");
1383
+ }
1384
+ ctx.logger.info("Auth Plugin started successfully");
1385
+ }
1386
+ async destroy() {
1387
+ this.authManager = null;
1388
+ }
1389
+ /**
1390
+ * Register authentication routes with HTTP server
1391
+ *
1392
+ * Uses better-auth's universal handler for all authentication requests.
1393
+ * This forwards all requests under basePath to better-auth, which handles:
1394
+ * - Email/password authentication
1395
+ * - OAuth providers (Google, GitHub, etc.)
1396
+ * - Session management
1397
+ * - Password reset
1398
+ * - Email verification
1399
+ * - 2FA, passkeys, magic links (if enabled)
1400
+ */
1401
+ registerAuthRoutes(httpServer, ctx) {
1402
+ if (!this.authManager) return;
1403
+ const basePath = this.options.basePath || "/api/v1/auth";
1404
+ if (!("getRawApp" in httpServer) || typeof httpServer.getRawApp !== "function") {
1405
+ ctx.logger.error("HTTP server does not support getRawApp() - wildcard routing requires Hono server");
1406
+ throw new Error(
1407
+ "AuthPlugin requires HonoServerPlugin for wildcard routing support. Please ensure HonoServerPlugin is loaded before AuthPlugin."
1408
+ );
1409
+ }
1410
+ const rawApp = httpServer.getRawApp();
1411
+ rawApp.all(`${basePath}/*`, async (c) => {
1412
+ try {
1413
+ const response = await this.authManager.handleRequest(c.req.raw);
1414
+ if (response.status >= 500) {
1415
+ try {
1416
+ const body = await response.clone().text();
1417
+ ctx.logger.error("[AuthPlugin] better-auth returned server error", new Error(`HTTP ${response.status}: ${body}`));
1418
+ } catch {
1419
+ ctx.logger.error("[AuthPlugin] better-auth returned server error", new Error(`HTTP ${response.status}: (unable to read body)`));
1420
+ }
1421
+ }
1422
+ return response;
1423
+ } catch (error) {
1424
+ const err = error instanceof Error ? error : new Error(String(error));
1425
+ ctx.logger.error("Auth request error:", err);
1426
+ return new Response(
1427
+ JSON.stringify({
1428
+ success: false,
1429
+ error: err.message
1430
+ }),
1431
+ {
1432
+ status: 500,
1433
+ headers: { "Content-Type": "application/json" }
1434
+ }
1435
+ );
1436
+ }
1437
+ });
1438
+ ctx.logger.info(`Auth routes registered: All requests under ${basePath}/* forwarded to better-auth`);
1439
+ }
1440
+ };
1358
1441
  // Annotate the CommonJS export names for ESM import in node:
1359
1442
  0 && (module.exports = {
1360
1443
  AUTH_ACCOUNT_CONFIG,