@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.mjs CHANGED
@@ -339,10 +339,28 @@ var AuthManager = class {
339
339
  verification: {
340
340
  ...AUTH_VERIFICATION_CONFIG
341
341
  },
342
- // Email configuration
342
+ // Social / OAuth providers
343
+ ...this.config.socialProviders ? { socialProviders: this.config.socialProviders } : {},
344
+ // Email and password configuration
343
345
  emailAndPassword: {
344
- enabled: true
346
+ enabled: this.config.emailAndPassword?.enabled ?? true,
347
+ ...this.config.emailAndPassword?.disableSignUp != null ? { disableSignUp: this.config.emailAndPassword.disableSignUp } : {},
348
+ ...this.config.emailAndPassword?.requireEmailVerification != null ? { requireEmailVerification: this.config.emailAndPassword.requireEmailVerification } : {},
349
+ ...this.config.emailAndPassword?.minPasswordLength != null ? { minPasswordLength: this.config.emailAndPassword.minPasswordLength } : {},
350
+ ...this.config.emailAndPassword?.maxPasswordLength != null ? { maxPasswordLength: this.config.emailAndPassword.maxPasswordLength } : {},
351
+ ...this.config.emailAndPassword?.resetPasswordTokenExpiresIn != null ? { resetPasswordTokenExpiresIn: this.config.emailAndPassword.resetPasswordTokenExpiresIn } : {},
352
+ ...this.config.emailAndPassword?.autoSignIn != null ? { autoSignIn: this.config.emailAndPassword.autoSignIn } : {},
353
+ ...this.config.emailAndPassword?.revokeSessionsOnPasswordReset != null ? { revokeSessionsOnPasswordReset: this.config.emailAndPassword.revokeSessionsOnPasswordReset } : {}
345
354
  },
355
+ // Email verification
356
+ ...this.config.emailVerification ? {
357
+ emailVerification: {
358
+ ...this.config.emailVerification.sendOnSignUp != null ? { sendOnSignUp: this.config.emailVerification.sendOnSignUp } : {},
359
+ ...this.config.emailVerification.sendOnSignIn != null ? { sendOnSignIn: this.config.emailVerification.sendOnSignIn } : {},
360
+ ...this.config.emailVerification.autoSignInAfterVerification != null ? { autoSignInAfterVerification: this.config.emailVerification.autoSignInAfterVerification } : {},
361
+ ...this.config.emailVerification.expiresIn != null ? { expiresIn: this.config.emailVerification.expiresIn } : {}
362
+ }
363
+ } : {},
346
364
  // Session configuration
347
365
  session: {
348
366
  ...AUTH_SESSION_CONFIG,
@@ -352,7 +370,18 @@ var AuthManager = class {
352
370
  // 1 day default
353
371
  },
354
372
  // better-auth plugins — registered based on AuthPluginConfig flags
355
- plugins: this.buildPluginList()
373
+ plugins: this.buildPluginList(),
374
+ // Trusted origins for CSRF protection (supports wildcards like "https://*.example.com")
375
+ ...this.config.trustedOrigins?.length ? { trustedOrigins: this.config.trustedOrigins } : {},
376
+ // Advanced options (cross-subdomain cookies, secure cookies, CSRF, etc.)
377
+ ...this.config.advanced ? {
378
+ advanced: {
379
+ ...this.config.advanced.crossSubDomainCookies ? { crossSubDomainCookies: this.config.advanced.crossSubDomainCookies } : {},
380
+ ...this.config.advanced.useSecureCookies != null ? { useSecureCookies: this.config.advanced.useSecureCookies } : {},
381
+ ...this.config.advanced.disableCSRFCheck != null ? { disableCSRFCheck: this.config.advanced.disableCSRFCheck } : {},
382
+ ...this.config.advanced.cookiePrefix != null ? { cookiePrefix: this.config.advanced.cookiePrefix } : {}
383
+ }
384
+ } : {}
356
385
  };
357
386
  return betterAuth(betterAuthConfig);
358
387
  }
@@ -422,6 +451,25 @@ var AuthManager = class {
422
451
  }
423
452
  return envSecret;
424
453
  }
454
+ /**
455
+ * Update the base URL at runtime.
456
+ *
457
+ * This **must** be called before the first request triggers lazy
458
+ * initialisation of the better-auth instance — typically from a
459
+ * `kernel:ready` hook where the actual server port is known.
460
+ *
461
+ * If the auth instance has already been created this is a no-op and
462
+ * a warning is emitted.
463
+ */
464
+ setRuntimeBaseUrl(url) {
465
+ if (this.auth) {
466
+ console.warn(
467
+ "[AuthManager] setRuntimeBaseUrl() called after the auth instance was already created \u2014 ignoring. Ensure this method is called before the first request."
468
+ );
469
+ return;
470
+ }
471
+ this.config = { ...this.config, baseUrl: url };
472
+ }
425
473
  /**
426
474
  * Get the underlying better-auth instance
427
475
  * Useful for advanced use cases
@@ -462,130 +510,6 @@ var AuthManager = class {
462
510
  }
463
511
  };
464
512
 
465
- // src/auth-plugin.ts
466
- var AuthPlugin = class {
467
- constructor(options = {}) {
468
- this.name = "com.objectstack.auth";
469
- this.type = "standard";
470
- this.version = "1.0.0";
471
- this.dependencies = [];
472
- this.authManager = null;
473
- this.options = {
474
- registerRoutes: true,
475
- basePath: "/api/v1/auth",
476
- ...options
477
- };
478
- }
479
- async init(ctx) {
480
- ctx.logger.info("Initializing Auth Plugin...");
481
- if (!this.options.secret) {
482
- throw new Error("AuthPlugin: secret is required");
483
- }
484
- const dataEngine = ctx.getService("data");
485
- if (!dataEngine) {
486
- ctx.logger.warn("No data engine service found - auth will use in-memory storage");
487
- }
488
- this.authManager = new AuthManager({
489
- ...this.options,
490
- dataEngine
491
- });
492
- ctx.registerService("auth", this.authManager);
493
- ctx.logger.info("Auth Plugin initialized successfully");
494
- }
495
- async start(ctx) {
496
- ctx.logger.info("Starting Auth Plugin...");
497
- if (!this.authManager) {
498
- throw new Error("Auth manager not initialized");
499
- }
500
- if (this.options.registerRoutes) {
501
- ctx.hook("kernel:ready", async () => {
502
- let httpServer = null;
503
- try {
504
- httpServer = ctx.getService("http-server");
505
- } catch {
506
- }
507
- if (httpServer) {
508
- this.registerAuthRoutes(httpServer, ctx);
509
- ctx.logger.info(`Auth routes registered at ${this.options.basePath}`);
510
- } else {
511
- ctx.logger.warn(
512
- "No HTTP server available \u2014 auth routes not registered. Auth service is still available for MSW/mock environments via HttpDispatcher."
513
- );
514
- }
515
- });
516
- }
517
- try {
518
- const ql = ctx.getService("objectql");
519
- if (ql && typeof ql.registerMiddleware === "function") {
520
- ql.registerMiddleware(async (opCtx, next) => {
521
- if (opCtx.context?.userId || opCtx.context?.isSystem) {
522
- return next();
523
- }
524
- await next();
525
- });
526
- ctx.logger.info("Auth middleware registered on ObjectQL engine");
527
- }
528
- } catch (_e) {
529
- ctx.logger.debug("ObjectQL engine not available, skipping auth middleware registration");
530
- }
531
- ctx.logger.info("Auth Plugin started successfully");
532
- }
533
- async destroy() {
534
- this.authManager = null;
535
- }
536
- /**
537
- * Register authentication routes with HTTP server
538
- *
539
- * Uses better-auth's universal handler for all authentication requests.
540
- * This forwards all requests under basePath to better-auth, which handles:
541
- * - Email/password authentication
542
- * - OAuth providers (Google, GitHub, etc.)
543
- * - Session management
544
- * - Password reset
545
- * - Email verification
546
- * - 2FA, passkeys, magic links (if enabled)
547
- */
548
- registerAuthRoutes(httpServer, ctx) {
549
- if (!this.authManager) return;
550
- const basePath = this.options.basePath || "/api/v1/auth";
551
- if (!("getRawApp" in httpServer) || typeof httpServer.getRawApp !== "function") {
552
- ctx.logger.error("HTTP server does not support getRawApp() - wildcard routing requires Hono server");
553
- throw new Error(
554
- "AuthPlugin requires HonoServerPlugin for wildcard routing support. Please ensure HonoServerPlugin is loaded before AuthPlugin."
555
- );
556
- }
557
- const rawApp = httpServer.getRawApp();
558
- rawApp.all(`${basePath}/*`, async (c) => {
559
- try {
560
- const response = await this.authManager.handleRequest(c.req.raw);
561
- if (response.status >= 500) {
562
- try {
563
- const body = await response.clone().text();
564
- ctx.logger.error("[AuthPlugin] better-auth returned server error", new Error(`HTTP ${response.status}: ${body}`));
565
- } catch {
566
- ctx.logger.error("[AuthPlugin] better-auth returned server error", new Error(`HTTP ${response.status}: (unable to read body)`));
567
- }
568
- }
569
- return response;
570
- } catch (error) {
571
- const err = error instanceof Error ? error : new Error(String(error));
572
- ctx.logger.error("Auth request error:", err);
573
- return new Response(
574
- JSON.stringify({
575
- success: false,
576
- error: err.message
577
- }),
578
- {
579
- status: 500,
580
- headers: { "Content-Type": "application/json" }
581
- }
582
- );
583
- }
584
- });
585
- ctx.logger.info(`Auth routes registered: All requests under ${basePath}/* forwarded to better-auth`);
586
- }
587
- };
588
-
589
513
  // src/objects/sys-user.object.ts
590
514
  import { ObjectSchema, Field } from "@objectstack/spec/data";
591
515
  var SysUser = ObjectSchema.create({
@@ -1295,6 +1219,165 @@ var SysTwoFactor = ObjectSchema11.create({
1295
1219
  mru: false
1296
1220
  }
1297
1221
  });
1222
+
1223
+ // src/auth-plugin.ts
1224
+ var AuthPlugin = class {
1225
+ constructor(options = {}) {
1226
+ this.name = "com.objectstack.auth";
1227
+ this.type = "standard";
1228
+ this.version = "1.0.0";
1229
+ this.dependencies = [];
1230
+ this.authManager = null;
1231
+ this.options = {
1232
+ registerRoutes: true,
1233
+ basePath: "/api/v1/auth",
1234
+ ...options
1235
+ };
1236
+ }
1237
+ async init(ctx) {
1238
+ ctx.logger.info("Initializing Auth Plugin...");
1239
+ if (!this.options.secret) {
1240
+ throw new Error("AuthPlugin: secret is required");
1241
+ }
1242
+ const dataEngine = ctx.getService("data");
1243
+ if (!dataEngine) {
1244
+ ctx.logger.warn("No data engine service found - auth will use in-memory storage");
1245
+ }
1246
+ this.authManager = new AuthManager({
1247
+ ...this.options,
1248
+ dataEngine
1249
+ });
1250
+ ctx.registerService("auth", this.authManager);
1251
+ ctx.registerService("app.com.objectstack.system", {
1252
+ id: "com.objectstack.system",
1253
+ name: "System",
1254
+ version: "1.0.0",
1255
+ type: "plugin",
1256
+ namespace: "sys",
1257
+ objects: [
1258
+ SysUser,
1259
+ SysSession,
1260
+ SysAccount,
1261
+ SysVerification,
1262
+ SysOrganization,
1263
+ SysMember,
1264
+ SysInvitation,
1265
+ SysTeam,
1266
+ SysTeamMember,
1267
+ SysApiKey,
1268
+ SysTwoFactor
1269
+ ]
1270
+ });
1271
+ ctx.logger.info("Auth Plugin initialized successfully");
1272
+ }
1273
+ async start(ctx) {
1274
+ ctx.logger.info("Starting Auth Plugin...");
1275
+ if (!this.authManager) {
1276
+ throw new Error("Auth manager not initialized");
1277
+ }
1278
+ if (this.options.registerRoutes) {
1279
+ ctx.hook("kernel:ready", async () => {
1280
+ let httpServer = null;
1281
+ try {
1282
+ httpServer = ctx.getService("http-server");
1283
+ } catch {
1284
+ }
1285
+ if (httpServer) {
1286
+ const serverWithPort = httpServer;
1287
+ if (this.authManager && typeof serverWithPort.getPort === "function") {
1288
+ const actualPort = serverWithPort.getPort();
1289
+ if (actualPort) {
1290
+ const configuredUrl = this.options.baseUrl || "http://localhost:3000";
1291
+ const configuredOrigin = new URL(configuredUrl).origin;
1292
+ const actualUrl = `http://localhost:${actualPort}`;
1293
+ if (configuredOrigin !== actualUrl) {
1294
+ this.authManager.setRuntimeBaseUrl(actualUrl);
1295
+ ctx.logger.info(
1296
+ `Auth baseUrl auto-updated to ${actualUrl} (configured: ${configuredUrl})`
1297
+ );
1298
+ }
1299
+ }
1300
+ }
1301
+ this.registerAuthRoutes(httpServer, ctx);
1302
+ ctx.logger.info(`Auth routes registered at ${this.options.basePath}`);
1303
+ } else {
1304
+ ctx.logger.warn(
1305
+ "No HTTP server available \u2014 auth routes not registered. Auth service is still available for MSW/mock environments via HttpDispatcher."
1306
+ );
1307
+ }
1308
+ });
1309
+ }
1310
+ try {
1311
+ const ql = ctx.getService("objectql");
1312
+ if (ql && typeof ql.registerMiddleware === "function") {
1313
+ ql.registerMiddleware(async (opCtx, next) => {
1314
+ if (opCtx.context?.userId || opCtx.context?.isSystem) {
1315
+ return next();
1316
+ }
1317
+ await next();
1318
+ });
1319
+ ctx.logger.info("Auth middleware registered on ObjectQL engine");
1320
+ }
1321
+ } catch (_e) {
1322
+ ctx.logger.debug("ObjectQL engine not available, skipping auth middleware registration");
1323
+ }
1324
+ ctx.logger.info("Auth Plugin started successfully");
1325
+ }
1326
+ async destroy() {
1327
+ this.authManager = null;
1328
+ }
1329
+ /**
1330
+ * Register authentication routes with HTTP server
1331
+ *
1332
+ * Uses better-auth's universal handler for all authentication requests.
1333
+ * This forwards all requests under basePath to better-auth, which handles:
1334
+ * - Email/password authentication
1335
+ * - OAuth providers (Google, GitHub, etc.)
1336
+ * - Session management
1337
+ * - Password reset
1338
+ * - Email verification
1339
+ * - 2FA, passkeys, magic links (if enabled)
1340
+ */
1341
+ registerAuthRoutes(httpServer, ctx) {
1342
+ if (!this.authManager) return;
1343
+ const basePath = this.options.basePath || "/api/v1/auth";
1344
+ if (!("getRawApp" in httpServer) || typeof httpServer.getRawApp !== "function") {
1345
+ ctx.logger.error("HTTP server does not support getRawApp() - wildcard routing requires Hono server");
1346
+ throw new Error(
1347
+ "AuthPlugin requires HonoServerPlugin for wildcard routing support. Please ensure HonoServerPlugin is loaded before AuthPlugin."
1348
+ );
1349
+ }
1350
+ const rawApp = httpServer.getRawApp();
1351
+ rawApp.all(`${basePath}/*`, async (c) => {
1352
+ try {
1353
+ const response = await this.authManager.handleRequest(c.req.raw);
1354
+ if (response.status >= 500) {
1355
+ try {
1356
+ const body = await response.clone().text();
1357
+ ctx.logger.error("[AuthPlugin] better-auth returned server error", new Error(`HTTP ${response.status}: ${body}`));
1358
+ } catch {
1359
+ ctx.logger.error("[AuthPlugin] better-auth returned server error", new Error(`HTTP ${response.status}: (unable to read body)`));
1360
+ }
1361
+ }
1362
+ return response;
1363
+ } catch (error) {
1364
+ const err = error instanceof Error ? error : new Error(String(error));
1365
+ ctx.logger.error("Auth request error:", err);
1366
+ return new Response(
1367
+ JSON.stringify({
1368
+ success: false,
1369
+ error: err.message
1370
+ }),
1371
+ {
1372
+ status: 500,
1373
+ headers: { "Content-Type": "application/json" }
1374
+ }
1375
+ );
1376
+ }
1377
+ });
1378
+ ctx.logger.info(`Auth routes registered: All requests under ${basePath}/* forwarded to better-auth`);
1379
+ }
1380
+ };
1298
1381
  export {
1299
1382
  AUTH_ACCOUNT_CONFIG,
1300
1383
  AUTH_INVITATION_SCHEMA,