@objectstack/plugin-auth 4.0.3 → 4.0.5

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 (40) hide show
  1. package/README.md +4 -1
  2. package/dist/index.d.mts +345 -19928
  3. package/dist/index.d.ts +345 -19928
  4. package/dist/index.js +411 -857
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +415 -837
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +35 -12
  9. package/.turbo/turbo-build.log +0 -78
  10. package/ARCHITECTURE.md +0 -176
  11. package/CHANGELOG.md +0 -325
  12. package/IMPLEMENTATION_SUMMARY.md +0 -192
  13. package/examples/basic-usage.ts +0 -107
  14. package/objectstack.config.ts +0 -24
  15. package/src/auth-manager.test.ts +0 -758
  16. package/src/auth-manager.ts +0 -338
  17. package/src/auth-plugin.test.ts +0 -443
  18. package/src/auth-plugin.ts +0 -292
  19. package/src/auth-schema-config.ts +0 -339
  20. package/src/index.ts +0 -16
  21. package/src/objectql-adapter.test.ts +0 -281
  22. package/src/objectql-adapter.ts +0 -279
  23. package/src/objects/auth-account.object.ts +0 -7
  24. package/src/objects/auth-session.object.ts +0 -7
  25. package/src/objects/auth-user.object.ts +0 -7
  26. package/src/objects/auth-verification.object.ts +0 -7
  27. package/src/objects/index.ts +0 -40
  28. package/src/objects/sys-account.object.ts +0 -111
  29. package/src/objects/sys-api-key.object.ts +0 -104
  30. package/src/objects/sys-invitation.object.ts +0 -93
  31. package/src/objects/sys-member.object.ts +0 -68
  32. package/src/objects/sys-organization.object.ts +0 -82
  33. package/src/objects/sys-session.object.ts +0 -84
  34. package/src/objects/sys-team-member.object.ts +0 -61
  35. package/src/objects/sys-team.object.ts +0 -69
  36. package/src/objects/sys-two-factor.object.ts +0 -73
  37. package/src/objects/sys-user-preference.object.ts +0 -82
  38. package/src/objects/sys-user.object.ts +0 -91
  39. package/src/objects/sys-verification.object.ts +0 -75
  40. package/tsconfig.json +0 -18
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,15 +17,30 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
21
31
  var index_exports = {};
22
32
  __export(index_exports, {
23
33
  AUTH_ACCOUNT_CONFIG: () => AUTH_ACCOUNT_CONFIG,
34
+ AUTH_DEVICE_CODE_SCHEMA: () => AUTH_DEVICE_CODE_SCHEMA,
24
35
  AUTH_INVITATION_SCHEMA: () => AUTH_INVITATION_SCHEMA,
36
+ AUTH_JWKS_SCHEMA: () => AUTH_JWKS_SCHEMA,
25
37
  AUTH_MEMBER_SCHEMA: () => AUTH_MEMBER_SCHEMA,
26
38
  AUTH_MODEL_TO_PROTOCOL: () => AUTH_MODEL_TO_PROTOCOL,
39
+ AUTH_OAUTH_ACCESS_TOKEN_SCHEMA: () => AUTH_OAUTH_ACCESS_TOKEN_SCHEMA,
40
+ AUTH_OAUTH_APPLICATION_SCHEMA: () => AUTH_OAUTH_APPLICATION_SCHEMA,
41
+ AUTH_OAUTH_CLIENT_SCHEMA: () => AUTH_OAUTH_CLIENT_SCHEMA,
42
+ AUTH_OAUTH_CONSENT_SCHEMA: () => AUTH_OAUTH_CONSENT_SCHEMA,
43
+ AUTH_OAUTH_REFRESH_TOKEN_SCHEMA: () => AUTH_OAUTH_REFRESH_TOKEN_SCHEMA,
27
44
  AUTH_ORGANIZATION_SCHEMA: () => AUTH_ORGANIZATION_SCHEMA,
28
45
  AUTH_ORG_SESSION_FIELDS: () => AUTH_ORG_SESSION_FIELDS,
29
46
  AUTH_SESSION_CONFIG: () => AUTH_SESSION_CONFIG,
@@ -33,24 +50,12 @@ __export(index_exports, {
33
50
  AUTH_TWO_FACTOR_USER_FIELDS: () => AUTH_TWO_FACTOR_USER_FIELDS,
34
51
  AUTH_USER_CONFIG: () => AUTH_USER_CONFIG,
35
52
  AUTH_VERIFICATION_CONFIG: () => AUTH_VERIFICATION_CONFIG,
36
- AuthAccount: () => SysAccount,
37
53
  AuthManager: () => AuthManager,
38
54
  AuthPlugin: () => AuthPlugin,
39
- AuthSession: () => SysSession,
40
- AuthUser: () => SysUser,
41
- AuthVerification: () => SysVerification,
42
- SysAccount: () => SysAccount,
43
- SysApiKey: () => SysApiKey,
44
- SysInvitation: () => SysInvitation,
45
- SysMember: () => SysMember,
46
- SysOrganization: () => SysOrganization,
47
- SysSession: () => SysSession,
48
- SysTeam: () => SysTeam,
49
- SysTeamMember: () => SysTeamMember,
50
- SysTwoFactor: () => SysTwoFactor,
51
- SysUser: () => SysUser,
52
- SysUserPreference: () => SysUserPreference,
53
- SysVerification: () => SysVerification,
55
+ buildDeviceAuthorizationPluginSchema: () => buildDeviceAuthorizationPluginSchema,
56
+ buildJwtPluginSchema: () => buildJwtPluginSchema,
57
+ buildOauthProviderPluginSchema: () => buildOauthProviderPluginSchema,
58
+ buildOidcProviderPluginSchema: () => buildOidcProviderPluginSchema,
54
59
  buildOrganizationPluginSchema: () => buildOrganizationPluginSchema,
55
60
  buildTwoFactorPluginSchema: () => buildTwoFactorPluginSchema,
56
61
  createObjectQLAdapter: () => createObjectQLAdapter,
@@ -59,11 +64,8 @@ __export(index_exports, {
59
64
  });
60
65
  module.exports = __toCommonJS(index_exports);
61
66
 
62
- // src/auth-manager.ts
63
- var import_better_auth = require("better-auth");
64
- var import_organization = require("better-auth/plugins/organization");
65
- var import_two_factor = require("better-auth/plugins/two-factor");
66
- var import_magic_link = require("better-auth/plugins/magic-link");
67
+ // src/auth-plugin.ts
68
+ var import_apps = require("@objectstack/platform-objects/apps");
67
69
 
68
70
  // src/objectql-adapter.ts
69
71
  var import_adapters = require("better-auth/adapters");
@@ -337,6 +339,81 @@ var AUTH_TWO_FACTOR_SCHEMA = {
337
339
  var AUTH_TWO_FACTOR_USER_FIELDS = {
338
340
  twoFactorEnabled: "two_factor_enabled"
339
341
  };
342
+ var AUTH_OAUTH_CLIENT_SCHEMA = {
343
+ modelName: import_system2.SystemObjectName.OAUTH_APPLICATION,
344
+ // 'sys_oauth_application'
345
+ fields: {
346
+ clientId: "client_id",
347
+ clientSecret: "client_secret",
348
+ skipConsent: "skip_consent",
349
+ enableEndSession: "enable_end_session",
350
+ subjectType: "subject_type",
351
+ userId: "user_id",
352
+ createdAt: "created_at",
353
+ updatedAt: "updated_at",
354
+ redirectUris: "redirect_uris",
355
+ postLogoutRedirectUris: "post_logout_redirect_uris",
356
+ tokenEndpointAuthMethod: "token_endpoint_auth_method",
357
+ grantTypes: "grant_types",
358
+ responseTypes: "response_types",
359
+ requirePKCE: "require_pkce",
360
+ softwareId: "software_id",
361
+ softwareVersion: "software_version",
362
+ softwareStatement: "software_statement",
363
+ referenceId: "reference_id"
364
+ }
365
+ };
366
+ var AUTH_OAUTH_APPLICATION_SCHEMA = AUTH_OAUTH_CLIENT_SCHEMA;
367
+ var AUTH_OAUTH_ACCESS_TOKEN_SCHEMA = {
368
+ modelName: import_system2.SystemObjectName.OAUTH_ACCESS_TOKEN,
369
+ // 'sys_oauth_access_token'
370
+ fields: {
371
+ clientId: "client_id",
372
+ sessionId: "session_id",
373
+ userId: "user_id",
374
+ referenceId: "reference_id",
375
+ refreshId: "refresh_id",
376
+ expiresAt: "expires_at",
377
+ createdAt: "created_at"
378
+ }
379
+ };
380
+ var AUTH_OAUTH_REFRESH_TOKEN_SCHEMA = {
381
+ modelName: import_system2.SystemObjectName.OAUTH_REFRESH_TOKEN,
382
+ // 'sys_oauth_refresh_token'
383
+ fields: {
384
+ clientId: "client_id",
385
+ sessionId: "session_id",
386
+ userId: "user_id",
387
+ referenceId: "reference_id",
388
+ expiresAt: "expires_at",
389
+ createdAt: "created_at",
390
+ authTime: "auth_time"
391
+ }
392
+ };
393
+ var AUTH_OAUTH_CONSENT_SCHEMA = {
394
+ modelName: import_system2.SystemObjectName.OAUTH_CONSENT,
395
+ // 'sys_oauth_consent'
396
+ fields: {
397
+ clientId: "client_id",
398
+ userId: "user_id",
399
+ referenceId: "reference_id",
400
+ createdAt: "created_at",
401
+ updatedAt: "updated_at"
402
+ }
403
+ };
404
+ var AUTH_DEVICE_CODE_SCHEMA = {
405
+ modelName: import_system2.SystemObjectName.DEVICE_CODE,
406
+ // 'sys_device_code'
407
+ fields: {
408
+ deviceCode: "device_code",
409
+ userCode: "user_code",
410
+ userId: "user_id",
411
+ expiresAt: "expires_at",
412
+ lastPolledAt: "last_polled_at",
413
+ pollingInterval: "polling_interval",
414
+ clientId: "client_id"
415
+ }
416
+ };
340
417
  function buildTwoFactorPluginSchema() {
341
418
  return {
342
419
  twoFactor: AUTH_TWO_FACTOR_SCHEMA,
@@ -357,6 +434,35 @@ function buildOrganizationPluginSchema() {
357
434
  }
358
435
  };
359
436
  }
437
+ var AUTH_JWKS_SCHEMA = {
438
+ modelName: import_system2.SystemObjectName.JWKS,
439
+ // 'sys_jwks'
440
+ fields: {
441
+ publicKey: "public_key",
442
+ privateKey: "private_key",
443
+ createdAt: "created_at",
444
+ expiresAt: "expires_at"
445
+ }
446
+ };
447
+ function buildJwtPluginSchema() {
448
+ return {
449
+ jwks: AUTH_JWKS_SCHEMA
450
+ };
451
+ }
452
+ function buildOauthProviderPluginSchema() {
453
+ return {
454
+ oauthClient: AUTH_OAUTH_CLIENT_SCHEMA,
455
+ oauthAccessToken: AUTH_OAUTH_ACCESS_TOKEN_SCHEMA,
456
+ oauthRefreshToken: AUTH_OAUTH_REFRESH_TOKEN_SCHEMA,
457
+ oauthConsent: AUTH_OAUTH_CONSENT_SCHEMA
458
+ };
459
+ }
460
+ var buildOidcProviderPluginSchema = buildOauthProviderPluginSchema;
461
+ function buildDeviceAuthorizationPluginSchema() {
462
+ return {
463
+ deviceCode: AUTH_DEVICE_CODE_SCHEMA
464
+ };
465
+ }
360
466
 
361
467
  // src/auth-manager.ts
362
468
  var AuthManager = class {
@@ -370,16 +476,18 @@ var AuthManager = class {
370
476
  /**
371
477
  * Get or create the better-auth instance (lazy initialization)
372
478
  */
373
- getOrCreateAuth() {
479
+ async getOrCreateAuth() {
374
480
  if (!this.auth) {
375
- this.auth = this.createAuthInstance();
481
+ this.auth = await this.createAuthInstance();
376
482
  }
377
483
  return this.auth;
378
484
  }
379
485
  /**
380
486
  * Create a better-auth instance from configuration
381
487
  */
382
- createAuthInstance() {
488
+ async createAuthInstance() {
489
+ const { betterAuth } = await import("better-auth");
490
+ const plugins = await this.buildPluginList();
383
491
  const betterAuthConfig = {
384
492
  // Base configuration
385
493
  secret: this.config.secret || this.generateSecret(),
@@ -431,9 +539,22 @@ var AuthManager = class {
431
539
  // 1 day default
432
540
  },
433
541
  // better-auth plugins — registered based on AuthPluginConfig flags
434
- plugins: this.buildPluginList(),
542
+ plugins,
435
543
  // Trusted origins for CSRF protection (supports wildcards like "https://*.example.com")
436
- ...this.config.trustedOrigins?.length ? { trustedOrigins: this.config.trustedOrigins } : {},
544
+ // Auto-includes origins from CORS_ORIGIN env var so CORS and CSRF stay in sync.
545
+ ...(() => {
546
+ const origins = [...this.config.trustedOrigins || []];
547
+ const corsOrigin = process.env.CORS_ORIGIN;
548
+ if (corsOrigin && corsOrigin !== "*") {
549
+ corsOrigin.split(",").map((s) => s.trim()).filter(Boolean).forEach((o) => {
550
+ if (!origins.includes(o)) origins.push(o);
551
+ });
552
+ }
553
+ if (!origins.length && (!corsOrigin || corsOrigin === "*")) {
554
+ origins.push("http://localhost:*");
555
+ }
556
+ return origins.length ? { trustedOrigins: origins } : {};
557
+ })(),
437
558
  // Advanced options (cross-subdomain cookies, secure cookies, CSRF, etc.)
438
559
  ...this.config.advanced ? {
439
560
  advanced: {
@@ -444,7 +565,7 @@ var AuthManager = class {
444
565
  }
445
566
  } : {}
446
567
  };
447
- return (0, import_better_auth.betterAuth)(betterAuthConfig);
568
+ return betterAuth(betterAuthConfig);
448
569
  }
449
570
  /**
450
571
  * Build the list of better-auth plugins based on AuthPluginConfig flags.
@@ -453,21 +574,52 @@ var AuthManager = class {
453
574
  * a `schema` option containing the appropriate snake_case field mappings,
454
575
  * so that `createAdapterFactory` transforms them automatically.
455
576
  */
456
- buildPluginList() {
457
- const pluginConfig = this.config.plugins;
577
+ async buildPluginList() {
578
+ const pluginConfig = this.config.plugins ?? {};
458
579
  const plugins = [];
459
- if (pluginConfig?.organization) {
460
- plugins.push((0, import_organization.organization)({
461
- schema: buildOrganizationPluginSchema()
580
+ const enabled = {
581
+ organization: pluginConfig.organization ?? true,
582
+ twoFactor: pluginConfig.twoFactor ?? false,
583
+ passkeys: pluginConfig.passkeys ?? false,
584
+ magicLink: pluginConfig.magicLink ?? false,
585
+ oidcProvider: pluginConfig.oidcProvider ?? false,
586
+ deviceAuthorization: pluginConfig.deviceAuthorization ?? false
587
+ };
588
+ const { bearer } = await import("better-auth/plugins/bearer");
589
+ plugins.push(bearer());
590
+ if (enabled.organization) {
591
+ const { organization } = await import("better-auth/plugins/organization");
592
+ plugins.push(organization({
593
+ schema: buildOrganizationPluginSchema(),
594
+ // Enable the team sub-feature so the framework's `sys_team` /
595
+ // `sys_team_member` tables (already declared in platform-objects)
596
+ // are actually wired up to better-auth's CRUD endpoints
597
+ // (`/organization/{create,update,remove,list}-team[s]` and
598
+ // `/organization/{add,remove,list}-team-member[s]`). The Account
599
+ // portal exposes a Teams page; without this flag those endpoints
600
+ // 404 and the section silently breaks.
601
+ teams: { enabled: true },
602
+ // No mailer is wired in framework yet — log the accept URL so
603
+ // operators / UI can fall back to copy-paste flows. Replace this
604
+ // with a real mail integration when available.
605
+ sendInvitationEmail: async ({ email, invitation, organization: org, inviter }) => {
606
+ const baseUrl = (this.config.baseUrl ?? "").replace(/\/$/, "");
607
+ const acceptUrl = `${baseUrl}/accept-invitation/${invitation.id}`;
608
+ console.warn(
609
+ `[AuthManager] Invitation email not configured. To: ${email} (org: ${org?.name ?? invitation.organizationId}, role: ${invitation.role}, inviter: ${inviter?.user?.email ?? "unknown"}) URL: ${acceptUrl}`
610
+ );
611
+ }
462
612
  }));
463
613
  }
464
- if (pluginConfig?.twoFactor) {
465
- plugins.push((0, import_two_factor.twoFactor)({
614
+ if (enabled.twoFactor) {
615
+ const { twoFactor } = await import("better-auth/plugins/two-factor");
616
+ plugins.push(twoFactor({
466
617
  schema: buildTwoFactorPluginSchema()
467
618
  }));
468
619
  }
469
- if (pluginConfig?.magicLink) {
470
- plugins.push((0, import_magic_link.magicLink)({
620
+ if (enabled.magicLink) {
621
+ const { magicLink } = await import("better-auth/plugins/magic-link");
622
+ plugins.push(magicLink({
471
623
  sendMagicLink: async ({ email, url }) => {
472
624
  console.warn(
473
625
  `[AuthManager] Magic-link requested for ${email} but no sendMagicLink handler configured. URL: ${url}`
@@ -475,6 +627,43 @@ var AuthManager = class {
475
627
  }
476
628
  }));
477
629
  }
630
+ if (this.config.oidcProviders?.length) {
631
+ const { genericOAuth } = await import("better-auth/plugins/generic-oauth");
632
+ plugins.push(genericOAuth({
633
+ config: this.config.oidcProviders.map((p) => ({
634
+ providerId: p.providerId,
635
+ ...p.discoveryUrl ? { discoveryUrl: p.discoveryUrl } : {},
636
+ ...p.issuer ? { issuer: p.issuer } : {},
637
+ ...p.authorizationUrl ? { authorizationUrl: p.authorizationUrl } : {},
638
+ ...p.tokenUrl ? { tokenUrl: p.tokenUrl } : {},
639
+ ...p.userInfoUrl ? { userInfoUrl: p.userInfoUrl } : {},
640
+ clientId: p.clientId,
641
+ clientSecret: p.clientSecret,
642
+ ...p.scopes ? { scopes: p.scopes } : {},
643
+ ...p.pkce != null ? { pkce: p.pkce } : {}
644
+ }))
645
+ }));
646
+ }
647
+ if (enabled.oidcProvider) {
648
+ const { jwt } = await import("better-auth/plugins");
649
+ plugins.push(jwt({ schema: buildJwtPluginSchema() }));
650
+ const { oauthProvider } = await import("@better-auth/oauth-provider");
651
+ const baseUrl = (this.config.baseUrl ?? "").replace(/\/$/, "");
652
+ plugins.push(oauthProvider({
653
+ // Account SPA renders both pages — see apps/account.
654
+ loginPage: `${baseUrl}/_account/login`,
655
+ consentPage: `${baseUrl}/_account/oauth/consent`,
656
+ schema: buildOauthProviderPluginSchema()
657
+ }));
658
+ }
659
+ if (enabled.deviceAuthorization) {
660
+ const { deviceAuthorization } = await import("better-auth/plugins/device-authorization");
661
+ const baseUrl = (this.config.baseUrl ?? "").replace(/\/$/, "");
662
+ plugins.push(deviceAuthorization({
663
+ verificationUri: `${baseUrl}/_account/auth/device`,
664
+ schema: buildDeviceAuthorizationPluginSchema()
665
+ }));
666
+ }
478
667
  return plugins;
479
668
  }
480
669
  /**
@@ -550,7 +739,7 @@ var AuthManager = class {
550
739
  * @returns Web standard Response object
551
740
  */
552
741
  async handleRequest(request) {
553
- const auth = this.getOrCreateAuth();
742
+ const auth = await this.getOrCreateAuth();
554
743
  const response = await auth.handler(request);
555
744
  if (response.status >= 500) {
556
745
  try {
@@ -566,779 +755,120 @@ var AuthManager = class {
566
755
  * Get the better-auth API for programmatic access
567
756
  * Use this for server-side operations (e.g., creating users, checking sessions)
568
757
  */
569
- get api() {
570
- return this.getOrCreateAuth().api;
758
+ async getApi() {
759
+ const auth = await this.getOrCreateAuth();
760
+ return auth.api;
571
761
  }
572
- };
573
-
574
- // src/objects/sys-user.object.ts
575
- var import_data = require("@objectstack/spec/data");
576
- var SysUser = import_data.ObjectSchema.create({
577
- namespace: "sys",
578
- name: "user",
579
- label: "User",
580
- pluralLabel: "Users",
581
- icon: "user",
582
- isSystem: true,
583
- description: "User accounts for authentication",
584
- titleFormat: "{name} ({email})",
585
- compactLayout: ["name", "email", "email_verified"],
586
- fields: {
587
- id: import_data.Field.text({
588
- label: "User ID",
589
- required: true,
590
- readonly: true
591
- }),
592
- created_at: import_data.Field.datetime({
593
- label: "Created At",
594
- defaultValue: "NOW()",
595
- readonly: true
596
- }),
597
- updated_at: import_data.Field.datetime({
598
- label: "Updated At",
599
- defaultValue: "NOW()",
600
- readonly: true
601
- }),
602
- email: import_data.Field.email({
603
- label: "Email",
604
- required: true,
605
- searchable: true
606
- }),
607
- email_verified: import_data.Field.boolean({
608
- label: "Email Verified",
609
- defaultValue: false
610
- }),
611
- name: import_data.Field.text({
612
- label: "Name",
613
- required: true,
614
- searchable: true,
615
- maxLength: 255
616
- }),
617
- image: import_data.Field.url({
618
- label: "Profile Image",
619
- required: false
620
- })
621
- },
622
- indexes: [
623
- { fields: ["email"], unique: true },
624
- { fields: ["created_at"], unique: false }
625
- ],
626
- enable: {
627
- trackHistory: true,
628
- searchable: true,
629
- apiEnabled: true,
630
- apiMethods: ["get", "list", "create", "update", "delete"],
631
- trash: true,
632
- mru: true
633
- },
634
- validations: [
635
- {
636
- name: "email_unique",
637
- type: "unique",
638
- severity: "error",
639
- message: "Email must be unique",
640
- fields: ["email"],
641
- caseSensitive: false
762
+ // ---------------------------------------------------------------------------
763
+ // Device Flow (CLI browser-based login)
764
+ //
765
+ // The device authorization flow (RFC 8628) is now handled entirely by
766
+ // better-auth's `device-authorization` plugin. Endpoints are exposed at
767
+ // `${basePath}/device/{code,token,approve,deny}` and persisted in
768
+ // `sys_device_code`. Enable via `plugins.deviceAuthorization: true` in
769
+ // AuthPluginConfig.
770
+ // ---------------------------------------------------------------------------
771
+ getPublicConfig() {
772
+ const socialProviders = [];
773
+ if (this.config.socialProviders) {
774
+ for (const [id, providerConfig] of Object.entries(this.config.socialProviders)) {
775
+ if (providerConfig.enabled !== false) {
776
+ const nameMap = {
777
+ google: "Google",
778
+ github: "GitHub",
779
+ microsoft: "Microsoft",
780
+ apple: "Apple",
781
+ facebook: "Facebook",
782
+ twitter: "Twitter",
783
+ discord: "Discord",
784
+ gitlab: "GitLab",
785
+ linkedin: "LinkedIn"
786
+ };
787
+ socialProviders.push({
788
+ id,
789
+ name: nameMap[id] || id.charAt(0).toUpperCase() + id.slice(1),
790
+ enabled: true,
791
+ type: "social"
792
+ });
793
+ }
794
+ }
642
795
  }
643
- ]
644
- });
645
-
646
- // src/objects/sys-session.object.ts
647
- var import_data2 = require("@objectstack/spec/data");
648
- var SysSession = import_data2.ObjectSchema.create({
649
- namespace: "sys",
650
- name: "session",
651
- label: "Session",
652
- pluralLabel: "Sessions",
653
- icon: "key",
654
- isSystem: true,
655
- description: "Active user sessions",
656
- titleFormat: "Session {token}",
657
- compactLayout: ["user_id", "expires_at", "ip_address"],
658
- fields: {
659
- id: import_data2.Field.text({
660
- label: "Session ID",
661
- required: true,
662
- readonly: true
663
- }),
664
- created_at: import_data2.Field.datetime({
665
- label: "Created At",
666
- defaultValue: "NOW()",
667
- readonly: true
668
- }),
669
- updated_at: import_data2.Field.datetime({
670
- label: "Updated At",
671
- defaultValue: "NOW()",
672
- readonly: true
673
- }),
674
- user_id: import_data2.Field.text({
675
- label: "User ID",
676
- required: true
677
- }),
678
- expires_at: import_data2.Field.datetime({
679
- label: "Expires At",
680
- required: true
681
- }),
682
- token: import_data2.Field.text({
683
- label: "Session Token",
684
- required: true
685
- }),
686
- ip_address: import_data2.Field.text({
687
- label: "IP Address",
688
- required: false,
689
- maxLength: 45
690
- // Support IPv6
691
- }),
692
- user_agent: import_data2.Field.textarea({
693
- label: "User Agent",
694
- required: false
695
- })
696
- },
697
- indexes: [
698
- { fields: ["token"], unique: true },
699
- { fields: ["user_id"], unique: false },
700
- { fields: ["expires_at"], unique: false }
701
- ],
702
- enable: {
703
- trackHistory: false,
704
- searchable: false,
705
- apiEnabled: true,
706
- apiMethods: ["get", "list", "create", "delete"],
707
- trash: false,
708
- mru: false
709
- }
710
- });
711
-
712
- // src/objects/sys-account.object.ts
713
- var import_data3 = require("@objectstack/spec/data");
714
- var SysAccount = import_data3.ObjectSchema.create({
715
- namespace: "sys",
716
- name: "account",
717
- label: "Account",
718
- pluralLabel: "Accounts",
719
- icon: "link",
720
- isSystem: true,
721
- description: "OAuth and authentication provider accounts",
722
- titleFormat: "{provider_id} - {account_id}",
723
- compactLayout: ["provider_id", "user_id", "account_id"],
724
- fields: {
725
- id: import_data3.Field.text({
726
- label: "Account ID",
727
- required: true,
728
- readonly: true
729
- }),
730
- created_at: import_data3.Field.datetime({
731
- label: "Created At",
732
- defaultValue: "NOW()",
733
- readonly: true
734
- }),
735
- updated_at: import_data3.Field.datetime({
736
- label: "Updated At",
737
- defaultValue: "NOW()",
738
- readonly: true
739
- }),
740
- provider_id: import_data3.Field.text({
741
- label: "Provider ID",
742
- required: true,
743
- description: "OAuth provider identifier (google, github, etc.)"
744
- }),
745
- account_id: import_data3.Field.text({
746
- label: "Provider Account ID",
747
- required: true,
748
- description: "User's ID in the provider's system"
749
- }),
750
- user_id: import_data3.Field.text({
751
- label: "User ID",
752
- required: true,
753
- description: "Link to user table"
754
- }),
755
- access_token: import_data3.Field.textarea({
756
- label: "Access Token",
757
- required: false
758
- }),
759
- refresh_token: import_data3.Field.textarea({
760
- label: "Refresh Token",
761
- required: false
762
- }),
763
- id_token: import_data3.Field.textarea({
764
- label: "ID Token",
765
- required: false
766
- }),
767
- access_token_expires_at: import_data3.Field.datetime({
768
- label: "Access Token Expires At",
769
- required: false
770
- }),
771
- refresh_token_expires_at: import_data3.Field.datetime({
772
- label: "Refresh Token Expires At",
773
- required: false
774
- }),
775
- scope: import_data3.Field.text({
776
- label: "OAuth Scope",
777
- required: false
778
- }),
779
- password: import_data3.Field.text({
780
- label: "Password Hash",
781
- required: false,
782
- description: "Hashed password for email/password provider"
783
- })
784
- },
785
- indexes: [
786
- { fields: ["user_id"], unique: false },
787
- { fields: ["provider_id", "account_id"], unique: true }
788
- ],
789
- enable: {
790
- trackHistory: false,
791
- searchable: false,
792
- apiEnabled: true,
793
- apiMethods: ["get", "list", "create", "update", "delete"],
794
- trash: true,
795
- mru: false
796
- }
797
- });
798
-
799
- // src/objects/sys-verification.object.ts
800
- var import_data4 = require("@objectstack/spec/data");
801
- var SysVerification = import_data4.ObjectSchema.create({
802
- namespace: "sys",
803
- name: "verification",
804
- label: "Verification",
805
- pluralLabel: "Verifications",
806
- icon: "shield-check",
807
- isSystem: true,
808
- description: "Email and phone verification tokens",
809
- titleFormat: "Verification for {identifier}",
810
- compactLayout: ["identifier", "expires_at", "created_at"],
811
- fields: {
812
- id: import_data4.Field.text({
813
- label: "Verification ID",
814
- required: true,
815
- readonly: true
816
- }),
817
- created_at: import_data4.Field.datetime({
818
- label: "Created At",
819
- defaultValue: "NOW()",
820
- readonly: true
821
- }),
822
- updated_at: import_data4.Field.datetime({
823
- label: "Updated At",
824
- defaultValue: "NOW()",
825
- readonly: true
826
- }),
827
- value: import_data4.Field.text({
828
- label: "Verification Token",
829
- required: true,
830
- description: "Token or code for verification"
831
- }),
832
- expires_at: import_data4.Field.datetime({
833
- label: "Expires At",
834
- required: true
835
- }),
836
- identifier: import_data4.Field.text({
837
- label: "Identifier",
838
- required: true,
839
- description: "Email address or phone number"
840
- })
841
- },
842
- indexes: [
843
- { fields: ["value"], unique: true },
844
- { fields: ["identifier"], unique: false },
845
- { fields: ["expires_at"], unique: false }
846
- ],
847
- enable: {
848
- trackHistory: false,
849
- searchable: false,
850
- apiEnabled: true,
851
- apiMethods: ["get", "create", "delete"],
852
- trash: false,
853
- mru: false
854
- }
855
- });
856
-
857
- // src/objects/sys-organization.object.ts
858
- var import_data5 = require("@objectstack/spec/data");
859
- var SysOrganization = import_data5.ObjectSchema.create({
860
- namespace: "sys",
861
- name: "organization",
862
- label: "Organization",
863
- pluralLabel: "Organizations",
864
- icon: "building-2",
865
- isSystem: true,
866
- description: "Organizations for multi-tenant grouping",
867
- titleFormat: "{name}",
868
- compactLayout: ["name", "slug", "created_at"],
869
- fields: {
870
- id: import_data5.Field.text({
871
- label: "Organization ID",
872
- required: true,
873
- readonly: true
874
- }),
875
- created_at: import_data5.Field.datetime({
876
- label: "Created At",
877
- defaultValue: "NOW()",
878
- readonly: true
879
- }),
880
- updated_at: import_data5.Field.datetime({
881
- label: "Updated At",
882
- defaultValue: "NOW()",
883
- readonly: true
884
- }),
885
- name: import_data5.Field.text({
886
- label: "Name",
887
- required: true,
888
- searchable: true,
889
- maxLength: 255
890
- }),
891
- slug: import_data5.Field.text({
892
- label: "Slug",
893
- required: false,
894
- maxLength: 255,
895
- description: "URL-friendly identifier"
896
- }),
897
- logo: import_data5.Field.url({
898
- label: "Logo",
899
- required: false
900
- }),
901
- metadata: import_data5.Field.textarea({
902
- label: "Metadata",
903
- required: false,
904
- description: "JSON-serialized organization metadata"
905
- })
906
- },
907
- indexes: [
908
- { fields: ["slug"], unique: true },
909
- { fields: ["name"] }
910
- ],
911
- enable: {
912
- trackHistory: true,
913
- searchable: true,
914
- apiEnabled: true,
915
- apiMethods: ["get", "list", "create", "update", "delete"],
916
- trash: true,
917
- mru: true
918
- }
919
- });
920
-
921
- // src/objects/sys-member.object.ts
922
- var import_data6 = require("@objectstack/spec/data");
923
- var SysMember = import_data6.ObjectSchema.create({
924
- namespace: "sys",
925
- name: "member",
926
- label: "Member",
927
- pluralLabel: "Members",
928
- icon: "user-check",
929
- isSystem: true,
930
- description: "Organization membership records",
931
- titleFormat: "{user_id} in {organization_id}",
932
- compactLayout: ["user_id", "organization_id", "role"],
933
- fields: {
934
- id: import_data6.Field.text({
935
- label: "Member ID",
936
- required: true,
937
- readonly: true
938
- }),
939
- created_at: import_data6.Field.datetime({
940
- label: "Created At",
941
- defaultValue: "NOW()",
942
- readonly: true
943
- }),
944
- organization_id: import_data6.Field.text({
945
- label: "Organization ID",
946
- required: true
947
- }),
948
- user_id: import_data6.Field.text({
949
- label: "User ID",
950
- required: true
951
- }),
952
- role: import_data6.Field.text({
953
- label: "Role",
954
- required: false,
955
- description: "Member role within the organization (e.g. admin, member)",
956
- maxLength: 100
957
- })
958
- },
959
- indexes: [
960
- { fields: ["organization_id", "user_id"], unique: true },
961
- { fields: ["user_id"] }
962
- ],
963
- enable: {
964
- trackHistory: true,
965
- searchable: false,
966
- apiEnabled: true,
967
- apiMethods: ["get", "list", "create", "update", "delete"],
968
- trash: false,
969
- mru: false
970
- }
971
- });
972
-
973
- // src/objects/sys-invitation.object.ts
974
- var import_data7 = require("@objectstack/spec/data");
975
- var SysInvitation = import_data7.ObjectSchema.create({
976
- namespace: "sys",
977
- name: "invitation",
978
- label: "Invitation",
979
- pluralLabel: "Invitations",
980
- icon: "mail",
981
- isSystem: true,
982
- description: "Organization invitations for user onboarding",
983
- titleFormat: "Invitation to {organization_id}",
984
- compactLayout: ["email", "organization_id", "status"],
985
- fields: {
986
- id: import_data7.Field.text({
987
- label: "Invitation ID",
988
- required: true,
989
- readonly: true
990
- }),
991
- created_at: import_data7.Field.datetime({
992
- label: "Created At",
993
- defaultValue: "NOW()",
994
- readonly: true
995
- }),
996
- organization_id: import_data7.Field.text({
997
- label: "Organization ID",
998
- required: true
999
- }),
1000
- email: import_data7.Field.email({
1001
- label: "Email",
1002
- required: true,
1003
- description: "Email address of the invited user"
1004
- }),
1005
- role: import_data7.Field.text({
1006
- label: "Role",
1007
- required: false,
1008
- maxLength: 100,
1009
- description: "Role to assign upon acceptance"
1010
- }),
1011
- status: import_data7.Field.select(["pending", "accepted", "rejected", "expired", "canceled"], {
1012
- label: "Status",
1013
- required: true,
1014
- defaultValue: "pending"
1015
- }),
1016
- inviter_id: import_data7.Field.text({
1017
- label: "Inviter ID",
1018
- required: true,
1019
- description: "User ID of the person who sent the invitation"
1020
- }),
1021
- expires_at: import_data7.Field.datetime({
1022
- label: "Expires At",
1023
- required: true
1024
- }),
1025
- team_id: import_data7.Field.text({
1026
- label: "Team ID",
1027
- required: false,
1028
- description: "Optional team to assign upon acceptance"
1029
- })
1030
- },
1031
- indexes: [
1032
- { fields: ["organization_id"] },
1033
- { fields: ["email"] },
1034
- { fields: ["expires_at"] }
1035
- ],
1036
- enable: {
1037
- trackHistory: true,
1038
- searchable: false,
1039
- apiEnabled: true,
1040
- apiMethods: ["get", "list", "create", "update", "delete"],
1041
- trash: false,
1042
- mru: false
1043
- }
1044
- });
1045
-
1046
- // src/objects/sys-team.object.ts
1047
- var import_data8 = require("@objectstack/spec/data");
1048
- var SysTeam = import_data8.ObjectSchema.create({
1049
- namespace: "sys",
1050
- name: "team",
1051
- label: "Team",
1052
- pluralLabel: "Teams",
1053
- icon: "users",
1054
- isSystem: true,
1055
- description: "Teams within organizations for fine-grained grouping",
1056
- titleFormat: "{name}",
1057
- compactLayout: ["name", "organization_id", "created_at"],
1058
- fields: {
1059
- id: import_data8.Field.text({
1060
- label: "Team ID",
1061
- required: true,
1062
- readonly: true
1063
- }),
1064
- created_at: import_data8.Field.datetime({
1065
- label: "Created At",
1066
- defaultValue: "NOW()",
1067
- readonly: true
1068
- }),
1069
- updated_at: import_data8.Field.datetime({
1070
- label: "Updated At",
1071
- defaultValue: "NOW()",
1072
- readonly: true
1073
- }),
1074
- name: import_data8.Field.text({
1075
- label: "Name",
1076
- required: true,
1077
- searchable: true,
1078
- maxLength: 255
1079
- }),
1080
- organization_id: import_data8.Field.text({
1081
- label: "Organization ID",
1082
- required: true
1083
- })
1084
- },
1085
- indexes: [
1086
- { fields: ["organization_id"] },
1087
- { fields: ["name", "organization_id"], unique: true }
1088
- ],
1089
- enable: {
1090
- trackHistory: true,
1091
- searchable: true,
1092
- apiEnabled: true,
1093
- apiMethods: ["get", "list", "create", "update", "delete"],
1094
- trash: true,
1095
- mru: false
1096
- }
1097
- });
1098
-
1099
- // src/objects/sys-team-member.object.ts
1100
- var import_data9 = require("@objectstack/spec/data");
1101
- var SysTeamMember = import_data9.ObjectSchema.create({
1102
- namespace: "sys",
1103
- name: "team_member",
1104
- label: "Team Member",
1105
- pluralLabel: "Team Members",
1106
- icon: "user-plus",
1107
- isSystem: true,
1108
- description: "Team membership records linking users to teams",
1109
- titleFormat: "{user_id} in {team_id}",
1110
- compactLayout: ["user_id", "team_id", "created_at"],
1111
- fields: {
1112
- id: import_data9.Field.text({
1113
- label: "Team Member ID",
1114
- required: true,
1115
- readonly: true
1116
- }),
1117
- created_at: import_data9.Field.datetime({
1118
- label: "Created At",
1119
- defaultValue: "NOW()",
1120
- readonly: true
1121
- }),
1122
- team_id: import_data9.Field.text({
1123
- label: "Team ID",
1124
- required: true
1125
- }),
1126
- user_id: import_data9.Field.text({
1127
- label: "User ID",
1128
- required: true
1129
- })
1130
- },
1131
- indexes: [
1132
- { fields: ["team_id", "user_id"], unique: true },
1133
- { fields: ["user_id"] }
1134
- ],
1135
- enable: {
1136
- trackHistory: true,
1137
- searchable: false,
1138
- apiEnabled: true,
1139
- apiMethods: ["get", "list", "create", "delete"],
1140
- trash: false,
1141
- mru: false
1142
- }
1143
- });
1144
-
1145
- // src/objects/sys-api-key.object.ts
1146
- var import_data10 = require("@objectstack/spec/data");
1147
- var SysApiKey = import_data10.ObjectSchema.create({
1148
- namespace: "sys",
1149
- name: "api_key",
1150
- label: "API Key",
1151
- pluralLabel: "API Keys",
1152
- icon: "key-round",
1153
- isSystem: true,
1154
- description: "API keys for programmatic access",
1155
- titleFormat: "{name}",
1156
- compactLayout: ["name", "user_id", "expires_at"],
1157
- fields: {
1158
- id: import_data10.Field.text({
1159
- label: "API Key ID",
1160
- required: true,
1161
- readonly: true
1162
- }),
1163
- created_at: import_data10.Field.datetime({
1164
- label: "Created At",
1165
- defaultValue: "NOW()",
1166
- readonly: true
1167
- }),
1168
- updated_at: import_data10.Field.datetime({
1169
- label: "Updated At",
1170
- defaultValue: "NOW()",
1171
- readonly: true
1172
- }),
1173
- name: import_data10.Field.text({
1174
- label: "Name",
1175
- required: true,
1176
- maxLength: 255,
1177
- description: "Human-readable label for the API key"
1178
- }),
1179
- key: import_data10.Field.text({
1180
- label: "Key",
1181
- required: true,
1182
- description: "Hashed API key value"
1183
- }),
1184
- prefix: import_data10.Field.text({
1185
- label: "Prefix",
1186
- required: false,
1187
- maxLength: 16,
1188
- description: 'Visible prefix for identifying the key (e.g., "osk_")'
1189
- }),
1190
- user_id: import_data10.Field.text({
1191
- label: "User ID",
1192
- required: true,
1193
- description: "Owner user of this API key"
1194
- }),
1195
- scopes: import_data10.Field.textarea({
1196
- label: "Scopes",
1197
- required: false,
1198
- description: "JSON array of permission scopes"
1199
- }),
1200
- expires_at: import_data10.Field.datetime({
1201
- label: "Expires At",
1202
- required: false
1203
- }),
1204
- last_used_at: import_data10.Field.datetime({
1205
- label: "Last Used At",
1206
- required: false
1207
- }),
1208
- revoked: import_data10.Field.boolean({
1209
- label: "Revoked",
1210
- defaultValue: false
1211
- })
1212
- },
1213
- indexes: [
1214
- { fields: ["key"], unique: true },
1215
- { fields: ["user_id"] },
1216
- { fields: ["prefix"] }
1217
- ],
1218
- enable: {
1219
- trackHistory: true,
1220
- searchable: false,
1221
- apiEnabled: true,
1222
- apiMethods: ["get", "list", "create", "update", "delete"],
1223
- trash: false,
1224
- mru: false
796
+ if (this.config.oidcProviders?.length) {
797
+ for (const p of this.config.oidcProviders) {
798
+ socialProviders.push({
799
+ id: p.providerId,
800
+ name: p.name ?? p.providerId.charAt(0).toUpperCase() + p.providerId.slice(1),
801
+ enabled: true,
802
+ type: "oidc"
803
+ });
804
+ }
805
+ }
806
+ const emailPasswordConfig = this.config.emailAndPassword ?? {};
807
+ const emailPassword = {
808
+ enabled: emailPasswordConfig.enabled !== false,
809
+ // Default to true
810
+ disableSignUp: emailPasswordConfig.disableSignUp ?? false,
811
+ requireEmailVerification: emailPasswordConfig.requireEmailVerification ?? false
812
+ };
813
+ const pluginConfig = this.config.plugins ?? {};
814
+ const features = {
815
+ twoFactor: pluginConfig.twoFactor ?? false,
816
+ passkeys: pluginConfig.passkeys ?? false,
817
+ magicLink: pluginConfig.magicLink ?? false,
818
+ organization: pluginConfig.organization ?? true,
819
+ oidcProvider: pluginConfig.oidcProvider ?? false,
820
+ deviceAuthorization: pluginConfig.deviceAuthorization ?? false
821
+ };
822
+ return {
823
+ emailPassword,
824
+ socialProviders,
825
+ features
826
+ };
1225
827
  }
1226
- });
1227
-
1228
- // src/objects/sys-two-factor.object.ts
1229
- var import_data11 = require("@objectstack/spec/data");
1230
- var SysTwoFactor = import_data11.ObjectSchema.create({
1231
- namespace: "sys",
1232
- name: "two_factor",
1233
- label: "Two Factor",
1234
- pluralLabel: "Two Factor Credentials",
1235
- icon: "smartphone",
1236
- isSystem: true,
1237
- description: "Two-factor authentication credentials",
1238
- titleFormat: "Two-factor for {user_id}",
1239
- compactLayout: ["user_id", "created_at"],
1240
- fields: {
1241
- id: import_data11.Field.text({
1242
- label: "Two Factor ID",
1243
- required: true,
1244
- readonly: true
1245
- }),
1246
- created_at: import_data11.Field.datetime({
1247
- label: "Created At",
1248
- defaultValue: "NOW()",
1249
- readonly: true
1250
- }),
1251
- updated_at: import_data11.Field.datetime({
1252
- label: "Updated At",
1253
- defaultValue: "NOW()",
1254
- readonly: true
1255
- }),
1256
- user_id: import_data11.Field.text({
1257
- label: "User ID",
1258
- required: true
1259
- }),
1260
- secret: import_data11.Field.text({
1261
- label: "Secret",
1262
- required: true,
1263
- description: "TOTP secret key"
1264
- }),
1265
- backup_codes: import_data11.Field.textarea({
1266
- label: "Backup Codes",
1267
- required: false,
1268
- description: "JSON-serialized backup recovery codes"
1269
- })
1270
- },
1271
- indexes: [
1272
- { fields: ["user_id"], unique: true }
1273
- ],
1274
- enable: {
1275
- trackHistory: false,
1276
- searchable: false,
1277
- apiEnabled: true,
1278
- apiMethods: ["get", "create", "update", "delete"],
1279
- trash: false,
1280
- mru: false
828
+ /**
829
+ * Returns the data engine wired into this auth manager. Used by route
830
+ * handlers (e.g. bootstrap-status) that need to query identity tables
831
+ * directly without going through better-auth.
832
+ */
833
+ getDataEngine() {
834
+ return this.config.dataEngine;
1281
835
  }
1282
- });
836
+ };
1283
837
 
1284
- // src/objects/sys-user-preference.object.ts
1285
- var import_data12 = require("@objectstack/spec/data");
1286
- var SysUserPreference = import_data12.ObjectSchema.create({
838
+ // src/manifest.ts
839
+ var import_identity = require("@objectstack/platform-objects/identity");
840
+ var AUTH_PLUGIN_ID = "com.objectstack.plugin-auth";
841
+ var AUTH_PLUGIN_VERSION = "3.0.1";
842
+ var authIdentityObjects = [
843
+ import_identity.SysUser,
844
+ import_identity.SysSession,
845
+ import_identity.SysAccount,
846
+ import_identity.SysVerification,
847
+ import_identity.SysOrganization,
848
+ import_identity.SysMember,
849
+ import_identity.SysInvitation,
850
+ import_identity.SysTeam,
851
+ import_identity.SysTeamMember,
852
+ import_identity.SysApiKey,
853
+ import_identity.SysTwoFactor,
854
+ import_identity.SysUserPreference,
855
+ import_identity.SysOauthApplication,
856
+ import_identity.SysOauthAccessToken,
857
+ import_identity.SysOauthRefreshToken,
858
+ import_identity.SysOauthConsent,
859
+ import_identity.SysJwks,
860
+ import_identity.SysDeviceCode
861
+ ];
862
+ var authPluginManifestHeader = {
863
+ id: AUTH_PLUGIN_ID,
1287
864
  namespace: "sys",
1288
- name: "user_preference",
1289
- label: "User Preference",
1290
- pluralLabel: "User Preferences",
1291
- icon: "settings",
1292
- isSystem: true,
1293
- description: "Per-user key-value preferences (theme, locale, etc.)",
1294
- titleFormat: "{key}",
1295
- compactLayout: ["user_id", "key"],
1296
- fields: {
1297
- id: import_data12.Field.text({
1298
- label: "Preference ID",
1299
- required: true,
1300
- readonly: true
1301
- }),
1302
- created_at: import_data12.Field.datetime({
1303
- label: "Created At",
1304
- defaultValue: "NOW()",
1305
- readonly: true
1306
- }),
1307
- updated_at: import_data12.Field.datetime({
1308
- label: "Updated At",
1309
- defaultValue: "NOW()",
1310
- readonly: true
1311
- }),
1312
- user_id: import_data12.Field.text({
1313
- label: "User ID",
1314
- required: true,
1315
- maxLength: 255,
1316
- description: "Owner user of this preference"
1317
- }),
1318
- key: import_data12.Field.text({
1319
- label: "Key",
1320
- required: true,
1321
- maxLength: 255,
1322
- description: "Preference key (e.g., theme, locale, plugin.ai.auto_save)"
1323
- }),
1324
- value: import_data12.Field.json({
1325
- label: "Value",
1326
- description: "Preference value (any JSON-serializable type)"
1327
- })
1328
- },
1329
- indexes: [
1330
- { fields: ["user_id", "key"], unique: true },
1331
- { fields: ["user_id"], unique: false }
1332
- ],
1333
- enable: {
1334
- trackHistory: false,
1335
- searchable: false,
1336
- apiEnabled: true,
1337
- apiMethods: ["get", "list", "create", "update", "delete"],
1338
- trash: false,
1339
- mru: false
1340
- }
1341
- });
865
+ version: AUTH_PLUGIN_VERSION,
866
+ type: "plugin",
867
+ scope: "system",
868
+ defaultDatasource: "cloud",
869
+ name: "Authentication & Identity Plugin",
870
+ description: "Core authentication objects for ObjectStack (User, Session, Account, Verification)"
871
+ };
1342
872
 
1343
873
  // src/auth-plugin.ts
1344
874
  var AuthPlugin = class {
@@ -1369,43 +899,27 @@ var AuthPlugin = class {
1369
899
  });
1370
900
  ctx.registerService("auth", this.authManager);
1371
901
  ctx.getService("manifest").register({
1372
- id: "com.objectstack.system",
1373
- name: "System",
1374
- version: "1.0.0",
1375
- type: "plugin",
1376
- namespace: "sys",
1377
- objects: [
1378
- SysUser,
1379
- SysSession,
1380
- SysAccount,
1381
- SysVerification,
1382
- SysOrganization,
1383
- SysMember,
1384
- SysInvitation,
1385
- SysTeam,
1386
- SysTeamMember,
1387
- SysApiKey,
1388
- SysTwoFactor,
1389
- SysUserPreference
1390
- ]
902
+ ...authPluginManifestHeader,
903
+ objects: authIdentityObjects,
904
+ // The platform Setup App is a static metadata artifact (lives in
905
+ // @objectstack/platform-objects/apps). plugin-auth is the natural
906
+ // owner of its registration since it loads first among the trio
907
+ // (auth + security + audit) that supplies the underlying objects.
908
+ apps: [import_apps.SETUP_APP],
909
+ // Curated list views and dashboards consumed by the Setup App's
910
+ // navigation entries. The manifest service does NOT auto-discover
911
+ // these from the app definition — they must be registered as
912
+ // explicit top-level arrays per ObjectStackDefinitionSchema.
913
+ views: [
914
+ import_apps.UsersView,
915
+ import_apps.OrganizationsView,
916
+ import_apps.RolesView,
917
+ import_apps.SessionsView,
918
+ import_apps.AuditLogsView,
919
+ import_apps.PackageInstallationsView
920
+ ],
921
+ dashboards: [import_apps.SystemOverviewDashboard, import_apps.SecurityOverviewDashboard]
1391
922
  });
1392
- try {
1393
- const setupNav = ctx.getService("setupNav");
1394
- if (setupNav) {
1395
- setupNav.contribute({
1396
- areaId: "area_administration",
1397
- items: [
1398
- { id: "nav_users", type: "object", label: "Users", objectName: "user", icon: "users", order: 10 },
1399
- { id: "nav_organizations", type: "object", label: "Organizations", objectName: "organization", icon: "building-2", order: 20 },
1400
- { id: "nav_teams", type: "object", label: "Teams", objectName: "team", icon: "users-round", order: 30 },
1401
- { id: "nav_api_keys", type: "object", label: "API Keys", objectName: "api_key", icon: "key", order: 40 },
1402
- { id: "nav_sessions", type: "object", label: "Sessions", objectName: "session", icon: "monitor", order: 50 }
1403
- ]
1404
- });
1405
- ctx.logger.info("Auth navigation items contributed to Setup App");
1406
- }
1407
- } catch {
1408
- }
1409
923
  ctx.logger.info("Auth Plugin initialized successfully");
1410
924
  }
1411
925
  async start(ctx) {
@@ -1428,7 +942,8 @@ var AuthPlugin = class {
1428
942
  const configuredUrl = this.options.baseUrl || "http://localhost:3000";
1429
943
  const configuredOrigin = new URL(configuredUrl).origin;
1430
944
  const actualUrl = `http://localhost:${actualPort}`;
1431
- if (configuredOrigin !== actualUrl) {
945
+ const configuredIsLocalhost = configuredOrigin.startsWith("http://localhost");
946
+ if (configuredIsLocalhost && configuredOrigin !== actualUrl) {
1432
947
  this.authManager.setRuntimeBaseUrl(actualUrl);
1433
948
  ctx.logger.info(
1434
949
  `Auth baseUrl auto-updated to ${actualUrl} (configured: ${configuredUrl})`
@@ -1486,6 +1001,28 @@ var AuthPlugin = class {
1486
1001
  );
1487
1002
  }
1488
1003
  const rawApp = httpServer.getRawApp();
1004
+ rawApp.get(`${basePath}/config`, (c) => {
1005
+ try {
1006
+ const config = this.authManager.getPublicConfig();
1007
+ return c.json({ success: true, data: config });
1008
+ } catch (error) {
1009
+ const err = error instanceof Error ? error : new Error(String(error));
1010
+ return c.json({ success: false, error: { code: "auth_config_error", message: err.message } }, 500);
1011
+ }
1012
+ });
1013
+ rawApp.get(`${basePath}/bootstrap-status`, async (c) => {
1014
+ try {
1015
+ const dataEngine = this.authManager.getDataEngine();
1016
+ if (!dataEngine) {
1017
+ return c.json({ hasOwner: true });
1018
+ }
1019
+ const count = await dataEngine.count("sys_user", {});
1020
+ return c.json({ hasOwner: (count ?? 0) > 0 });
1021
+ } catch (error) {
1022
+ ctx.logger.warn("[AuthPlugin] bootstrap-status check failed; assuming bootstrapped", error);
1023
+ return c.json({ hasOwner: true });
1024
+ }
1025
+ });
1489
1026
  rawApp.all(`${basePath}/*`, async (c) => {
1490
1027
  try {
1491
1028
  const response = await this.authManager.handleRequest(c.req.raw);
@@ -1513,15 +1050,44 @@ var AuthPlugin = class {
1513
1050
  );
1514
1051
  }
1515
1052
  });
1053
+ if (this.options.plugins?.oidcProvider) {
1054
+ void this.registerOidcDiscoveryRoutes(rawApp, ctx).catch((error) => {
1055
+ ctx.logger.error("Failed to register OIDC discovery routes", error);
1056
+ });
1057
+ }
1516
1058
  ctx.logger.info(`Auth routes registered: All requests under ${basePath}/* forwarded to better-auth`);
1517
1059
  }
1060
+ /**
1061
+ * Mount the OIDC / OAuth 2.0 well-known discovery documents at the root
1062
+ * URL. Required by RFC 8414 §3 and OpenID Connect Discovery 1.0 §4 — the
1063
+ * documents must live at `/.well-known/{oauth-authorization-server,openid-configuration}`
1064
+ * relative to the issuer, not under the auth basePath.
1065
+ */
1066
+ async registerOidcDiscoveryRoutes(rawApp, ctx) {
1067
+ const auth = await this.authManager.getAuthInstance();
1068
+ const { oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata } = await import("@better-auth/oauth-provider");
1069
+ const authServerHandler = oauthProviderAuthServerMetadata(auth);
1070
+ const openidConfigHandler = oauthProviderOpenIdConfigMetadata(auth);
1071
+ rawApp.get("/.well-known/oauth-authorization-server", (c) => authServerHandler(c.req.raw));
1072
+ rawApp.get("/.well-known/openid-configuration", (c) => openidConfigHandler(c.req.raw));
1073
+ ctx.logger.info(
1074
+ "OIDC discovery endpoints mounted at /.well-known/{oauth-authorization-server,openid-configuration}"
1075
+ );
1076
+ }
1518
1077
  };
1519
1078
  // Annotate the CommonJS export names for ESM import in node:
1520
1079
  0 && (module.exports = {
1521
1080
  AUTH_ACCOUNT_CONFIG,
1081
+ AUTH_DEVICE_CODE_SCHEMA,
1522
1082
  AUTH_INVITATION_SCHEMA,
1083
+ AUTH_JWKS_SCHEMA,
1523
1084
  AUTH_MEMBER_SCHEMA,
1524
1085
  AUTH_MODEL_TO_PROTOCOL,
1086
+ AUTH_OAUTH_ACCESS_TOKEN_SCHEMA,
1087
+ AUTH_OAUTH_APPLICATION_SCHEMA,
1088
+ AUTH_OAUTH_CLIENT_SCHEMA,
1089
+ AUTH_OAUTH_CONSENT_SCHEMA,
1090
+ AUTH_OAUTH_REFRESH_TOKEN_SCHEMA,
1525
1091
  AUTH_ORGANIZATION_SCHEMA,
1526
1092
  AUTH_ORG_SESSION_FIELDS,
1527
1093
  AUTH_SESSION_CONFIG,
@@ -1531,24 +1097,12 @@ var AuthPlugin = class {
1531
1097
  AUTH_TWO_FACTOR_USER_FIELDS,
1532
1098
  AUTH_USER_CONFIG,
1533
1099
  AUTH_VERIFICATION_CONFIG,
1534
- AuthAccount,
1535
1100
  AuthManager,
1536
1101
  AuthPlugin,
1537
- AuthSession,
1538
- AuthUser,
1539
- AuthVerification,
1540
- SysAccount,
1541
- SysApiKey,
1542
- SysInvitation,
1543
- SysMember,
1544
- SysOrganization,
1545
- SysSession,
1546
- SysTeam,
1547
- SysTeamMember,
1548
- SysTwoFactor,
1549
- SysUser,
1550
- SysUserPreference,
1551
- SysVerification,
1102
+ buildDeviceAuthorizationPluginSchema,
1103
+ buildJwtPluginSchema,
1104
+ buildOauthProviderPluginSchema,
1105
+ buildOidcProviderPluginSchema,
1552
1106
  buildOrganizationPluginSchema,
1553
1107
  buildTwoFactorPluginSchema,
1554
1108
  createObjectQLAdapter,