@objectstack/plugin-auth 4.0.4 → 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 +332 -19942
  3. package/dist/index.d.ts +332 -19942
  4. package/dist/index.js +351 -882
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +355 -862
  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 -333
  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 -883
  16. package/src/auth-manager.ts +0 -419
  17. package/src/auth-plugin.test.ts +0 -446
  18. package/src/auth-plugin.ts +0 -314
  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.mjs CHANGED
@@ -1,8 +1,15 @@
1
- // src/auth-manager.ts
2
- import { betterAuth } from "better-auth";
3
- import { organization } from "better-auth/plugins/organization";
4
- import { twoFactor } from "better-auth/plugins/two-factor";
5
- import { magicLink } from "better-auth/plugins/magic-link";
1
+ // src/auth-plugin.ts
2
+ import {
3
+ SETUP_APP,
4
+ SystemOverviewDashboard,
5
+ SecurityOverviewDashboard,
6
+ UsersView,
7
+ OrganizationsView,
8
+ RolesView,
9
+ SessionsView,
10
+ AuditLogsView,
11
+ PackageInstallationsView
12
+ } from "@objectstack/platform-objects/apps";
6
13
 
7
14
  // src/objectql-adapter.ts
8
15
  import { createAdapterFactory } from "better-auth/adapters";
@@ -276,6 +283,81 @@ var AUTH_TWO_FACTOR_SCHEMA = {
276
283
  var AUTH_TWO_FACTOR_USER_FIELDS = {
277
284
  twoFactorEnabled: "two_factor_enabled"
278
285
  };
286
+ var AUTH_OAUTH_CLIENT_SCHEMA = {
287
+ modelName: SystemObjectName2.OAUTH_APPLICATION,
288
+ // 'sys_oauth_application'
289
+ fields: {
290
+ clientId: "client_id",
291
+ clientSecret: "client_secret",
292
+ skipConsent: "skip_consent",
293
+ enableEndSession: "enable_end_session",
294
+ subjectType: "subject_type",
295
+ userId: "user_id",
296
+ createdAt: "created_at",
297
+ updatedAt: "updated_at",
298
+ redirectUris: "redirect_uris",
299
+ postLogoutRedirectUris: "post_logout_redirect_uris",
300
+ tokenEndpointAuthMethod: "token_endpoint_auth_method",
301
+ grantTypes: "grant_types",
302
+ responseTypes: "response_types",
303
+ requirePKCE: "require_pkce",
304
+ softwareId: "software_id",
305
+ softwareVersion: "software_version",
306
+ softwareStatement: "software_statement",
307
+ referenceId: "reference_id"
308
+ }
309
+ };
310
+ var AUTH_OAUTH_APPLICATION_SCHEMA = AUTH_OAUTH_CLIENT_SCHEMA;
311
+ var AUTH_OAUTH_ACCESS_TOKEN_SCHEMA = {
312
+ modelName: SystemObjectName2.OAUTH_ACCESS_TOKEN,
313
+ // 'sys_oauth_access_token'
314
+ fields: {
315
+ clientId: "client_id",
316
+ sessionId: "session_id",
317
+ userId: "user_id",
318
+ referenceId: "reference_id",
319
+ refreshId: "refresh_id",
320
+ expiresAt: "expires_at",
321
+ createdAt: "created_at"
322
+ }
323
+ };
324
+ var AUTH_OAUTH_REFRESH_TOKEN_SCHEMA = {
325
+ modelName: SystemObjectName2.OAUTH_REFRESH_TOKEN,
326
+ // 'sys_oauth_refresh_token'
327
+ fields: {
328
+ clientId: "client_id",
329
+ sessionId: "session_id",
330
+ userId: "user_id",
331
+ referenceId: "reference_id",
332
+ expiresAt: "expires_at",
333
+ createdAt: "created_at",
334
+ authTime: "auth_time"
335
+ }
336
+ };
337
+ var AUTH_OAUTH_CONSENT_SCHEMA = {
338
+ modelName: SystemObjectName2.OAUTH_CONSENT,
339
+ // 'sys_oauth_consent'
340
+ fields: {
341
+ clientId: "client_id",
342
+ userId: "user_id",
343
+ referenceId: "reference_id",
344
+ createdAt: "created_at",
345
+ updatedAt: "updated_at"
346
+ }
347
+ };
348
+ var AUTH_DEVICE_CODE_SCHEMA = {
349
+ modelName: SystemObjectName2.DEVICE_CODE,
350
+ // 'sys_device_code'
351
+ fields: {
352
+ deviceCode: "device_code",
353
+ userCode: "user_code",
354
+ userId: "user_id",
355
+ expiresAt: "expires_at",
356
+ lastPolledAt: "last_polled_at",
357
+ pollingInterval: "polling_interval",
358
+ clientId: "client_id"
359
+ }
360
+ };
279
361
  function buildTwoFactorPluginSchema() {
280
362
  return {
281
363
  twoFactor: AUTH_TWO_FACTOR_SCHEMA,
@@ -296,6 +378,35 @@ function buildOrganizationPluginSchema() {
296
378
  }
297
379
  };
298
380
  }
381
+ var AUTH_JWKS_SCHEMA = {
382
+ modelName: SystemObjectName2.JWKS,
383
+ // 'sys_jwks'
384
+ fields: {
385
+ publicKey: "public_key",
386
+ privateKey: "private_key",
387
+ createdAt: "created_at",
388
+ expiresAt: "expires_at"
389
+ }
390
+ };
391
+ function buildJwtPluginSchema() {
392
+ return {
393
+ jwks: AUTH_JWKS_SCHEMA
394
+ };
395
+ }
396
+ function buildOauthProviderPluginSchema() {
397
+ return {
398
+ oauthClient: AUTH_OAUTH_CLIENT_SCHEMA,
399
+ oauthAccessToken: AUTH_OAUTH_ACCESS_TOKEN_SCHEMA,
400
+ oauthRefreshToken: AUTH_OAUTH_REFRESH_TOKEN_SCHEMA,
401
+ oauthConsent: AUTH_OAUTH_CONSENT_SCHEMA
402
+ };
403
+ }
404
+ var buildOidcProviderPluginSchema = buildOauthProviderPluginSchema;
405
+ function buildDeviceAuthorizationPluginSchema() {
406
+ return {
407
+ deviceCode: AUTH_DEVICE_CODE_SCHEMA
408
+ };
409
+ }
299
410
 
300
411
  // src/auth-manager.ts
301
412
  var AuthManager = class {
@@ -309,16 +420,18 @@ var AuthManager = class {
309
420
  /**
310
421
  * Get or create the better-auth instance (lazy initialization)
311
422
  */
312
- getOrCreateAuth() {
423
+ async getOrCreateAuth() {
313
424
  if (!this.auth) {
314
- this.auth = this.createAuthInstance();
425
+ this.auth = await this.createAuthInstance();
315
426
  }
316
427
  return this.auth;
317
428
  }
318
429
  /**
319
430
  * Create a better-auth instance from configuration
320
431
  */
321
- createAuthInstance() {
432
+ async createAuthInstance() {
433
+ const { betterAuth } = await import("better-auth");
434
+ const plugins = await this.buildPluginList();
322
435
  const betterAuthConfig = {
323
436
  // Base configuration
324
437
  secret: this.config.secret || this.generateSecret(),
@@ -370,7 +483,7 @@ var AuthManager = class {
370
483
  // 1 day default
371
484
  },
372
485
  // better-auth plugins — registered based on AuthPluginConfig flags
373
- plugins: this.buildPluginList(),
486
+ plugins,
374
487
  // Trusted origins for CSRF protection (supports wildcards like "https://*.example.com")
375
488
  // Auto-includes origins from CORS_ORIGIN env var so CORS and CSRF stay in sync.
376
489
  ...(() => {
@@ -405,20 +518,51 @@ var AuthManager = class {
405
518
  * a `schema` option containing the appropriate snake_case field mappings,
406
519
  * so that `createAdapterFactory` transforms them automatically.
407
520
  */
408
- buildPluginList() {
409
- const pluginConfig = this.config.plugins;
521
+ async buildPluginList() {
522
+ const pluginConfig = this.config.plugins ?? {};
410
523
  const plugins = [];
411
- if (pluginConfig?.organization) {
524
+ const enabled = {
525
+ organization: pluginConfig.organization ?? true,
526
+ twoFactor: pluginConfig.twoFactor ?? false,
527
+ passkeys: pluginConfig.passkeys ?? false,
528
+ magicLink: pluginConfig.magicLink ?? false,
529
+ oidcProvider: pluginConfig.oidcProvider ?? false,
530
+ deviceAuthorization: pluginConfig.deviceAuthorization ?? false
531
+ };
532
+ const { bearer } = await import("better-auth/plugins/bearer");
533
+ plugins.push(bearer());
534
+ if (enabled.organization) {
535
+ const { organization } = await import("better-auth/plugins/organization");
412
536
  plugins.push(organization({
413
- schema: buildOrganizationPluginSchema()
537
+ schema: buildOrganizationPluginSchema(),
538
+ // Enable the team sub-feature so the framework's `sys_team` /
539
+ // `sys_team_member` tables (already declared in platform-objects)
540
+ // are actually wired up to better-auth's CRUD endpoints
541
+ // (`/organization/{create,update,remove,list}-team[s]` and
542
+ // `/organization/{add,remove,list}-team-member[s]`). The Account
543
+ // portal exposes a Teams page; without this flag those endpoints
544
+ // 404 and the section silently breaks.
545
+ teams: { enabled: true },
546
+ // No mailer is wired in framework yet — log the accept URL so
547
+ // operators / UI can fall back to copy-paste flows. Replace this
548
+ // with a real mail integration when available.
549
+ sendInvitationEmail: async ({ email, invitation, organization: org, inviter }) => {
550
+ const baseUrl = (this.config.baseUrl ?? "").replace(/\/$/, "");
551
+ const acceptUrl = `${baseUrl}/accept-invitation/${invitation.id}`;
552
+ console.warn(
553
+ `[AuthManager] Invitation email not configured. To: ${email} (org: ${org?.name ?? invitation.organizationId}, role: ${invitation.role}, inviter: ${inviter?.user?.email ?? "unknown"}) URL: ${acceptUrl}`
554
+ );
555
+ }
414
556
  }));
415
557
  }
416
- if (pluginConfig?.twoFactor) {
558
+ if (enabled.twoFactor) {
559
+ const { twoFactor } = await import("better-auth/plugins/two-factor");
417
560
  plugins.push(twoFactor({
418
561
  schema: buildTwoFactorPluginSchema()
419
562
  }));
420
563
  }
421
- if (pluginConfig?.magicLink) {
564
+ if (enabled.magicLink) {
565
+ const { magicLink } = await import("better-auth/plugins/magic-link");
422
566
  plugins.push(magicLink({
423
567
  sendMagicLink: async ({ email, url }) => {
424
568
  console.warn(
@@ -427,6 +571,43 @@ var AuthManager = class {
427
571
  }
428
572
  }));
429
573
  }
574
+ if (this.config.oidcProviders?.length) {
575
+ const { genericOAuth } = await import("better-auth/plugins/generic-oauth");
576
+ plugins.push(genericOAuth({
577
+ config: this.config.oidcProviders.map((p) => ({
578
+ providerId: p.providerId,
579
+ ...p.discoveryUrl ? { discoveryUrl: p.discoveryUrl } : {},
580
+ ...p.issuer ? { issuer: p.issuer } : {},
581
+ ...p.authorizationUrl ? { authorizationUrl: p.authorizationUrl } : {},
582
+ ...p.tokenUrl ? { tokenUrl: p.tokenUrl } : {},
583
+ ...p.userInfoUrl ? { userInfoUrl: p.userInfoUrl } : {},
584
+ clientId: p.clientId,
585
+ clientSecret: p.clientSecret,
586
+ ...p.scopes ? { scopes: p.scopes } : {},
587
+ ...p.pkce != null ? { pkce: p.pkce } : {}
588
+ }))
589
+ }));
590
+ }
591
+ if (enabled.oidcProvider) {
592
+ const { jwt } = await import("better-auth/plugins");
593
+ plugins.push(jwt({ schema: buildJwtPluginSchema() }));
594
+ const { oauthProvider } = await import("@better-auth/oauth-provider");
595
+ const baseUrl = (this.config.baseUrl ?? "").replace(/\/$/, "");
596
+ plugins.push(oauthProvider({
597
+ // Account SPA renders both pages — see apps/account.
598
+ loginPage: `${baseUrl}/_account/login`,
599
+ consentPage: `${baseUrl}/_account/oauth/consent`,
600
+ schema: buildOauthProviderPluginSchema()
601
+ }));
602
+ }
603
+ if (enabled.deviceAuthorization) {
604
+ const { deviceAuthorization } = await import("better-auth/plugins/device-authorization");
605
+ const baseUrl = (this.config.baseUrl ?? "").replace(/\/$/, "");
606
+ plugins.push(deviceAuthorization({
607
+ verificationUri: `${baseUrl}/_account/auth/device`,
608
+ schema: buildDeviceAuthorizationPluginSchema()
609
+ }));
610
+ }
430
611
  return plugins;
431
612
  }
432
613
  /**
@@ -502,7 +683,7 @@ var AuthManager = class {
502
683
  * @returns Web standard Response object
503
684
  */
504
685
  async handleRequest(request) {
505
- const auth = this.getOrCreateAuth();
686
+ const auth = await this.getOrCreateAuth();
506
687
  const response = await auth.handler(request);
507
688
  if (response.status >= 500) {
508
689
  try {
@@ -518,18 +699,19 @@ var AuthManager = class {
518
699
  * Get the better-auth API for programmatic access
519
700
  * Use this for server-side operations (e.g., creating users, checking sessions)
520
701
  */
521
- get api() {
522
- return this.getOrCreateAuth().api;
702
+ async getApi() {
703
+ const auth = await this.getOrCreateAuth();
704
+ return auth.api;
523
705
  }
524
- /**
525
- * Get public authentication configuration
526
- * Returns safe, non-sensitive configuration that can be exposed to the frontend
527
- *
528
- * This allows the frontend to discover:
529
- * - Which social/OAuth providers are available
530
- * - Whether email/password login is enabled
531
- * - Which advanced features are enabled (2FA, magic links, etc.)
532
- */
706
+ // ---------------------------------------------------------------------------
707
+ // Device Flow (CLI browser-based login)
708
+ //
709
+ // The device authorization flow (RFC 8628) is now handled entirely by
710
+ // better-auth's `device-authorization` plugin. Endpoints are exposed at
711
+ // `${basePath}/device/{code,token,approve,deny}` and persisted in
712
+ // `sys_device_code`. Enable via `plugins.deviceAuthorization: true` in
713
+ // AuthPluginConfig.
714
+ // ---------------------------------------------------------------------------
533
715
  getPublicConfig() {
534
716
  const socialProviders = [];
535
717
  if (this.config.socialProviders) {
@@ -549,11 +731,22 @@ var AuthManager = class {
549
731
  socialProviders.push({
550
732
  id,
551
733
  name: nameMap[id] || id.charAt(0).toUpperCase() + id.slice(1),
552
- enabled: true
734
+ enabled: true,
735
+ type: "social"
553
736
  });
554
737
  }
555
738
  }
556
739
  }
740
+ if (this.config.oidcProviders?.length) {
741
+ for (const p of this.config.oidcProviders) {
742
+ socialProviders.push({
743
+ id: p.providerId,
744
+ name: p.name ?? p.providerId.charAt(0).toUpperCase() + p.providerId.slice(1),
745
+ enabled: true,
746
+ type: "oidc"
747
+ });
748
+ }
749
+ }
557
750
  const emailPasswordConfig = this.config.emailAndPassword ?? {};
558
751
  const emailPassword = {
559
752
  enabled: emailPasswordConfig.enabled !== false,
@@ -566,7 +759,9 @@ var AuthManager = class {
566
759
  twoFactor: pluginConfig.twoFactor ?? false,
567
760
  passkeys: pluginConfig.passkeys ?? false,
568
761
  magicLink: pluginConfig.magicLink ?? false,
569
- organization: pluginConfig.organization ?? false
762
+ organization: pluginConfig.organization ?? true,
763
+ oidcProvider: pluginConfig.oidcProvider ?? false,
764
+ deviceAuthorization: pluginConfig.deviceAuthorization ?? false
570
765
  };
571
766
  return {
572
767
  emailPassword,
@@ -574,776 +769,69 @@ var AuthManager = class {
574
769
  features
575
770
  };
576
771
  }
577
- };
578
-
579
- // src/objects/sys-user.object.ts
580
- import { ObjectSchema, Field } from "@objectstack/spec/data";
581
- var SysUser = ObjectSchema.create({
582
- namespace: "sys",
583
- name: "user",
584
- label: "User",
585
- pluralLabel: "Users",
586
- icon: "user",
587
- isSystem: true,
588
- description: "User accounts for authentication",
589
- titleFormat: "{name} ({email})",
590
- compactLayout: ["name", "email", "email_verified"],
591
- fields: {
592
- id: Field.text({
593
- label: "User ID",
594
- required: true,
595
- readonly: true
596
- }),
597
- created_at: Field.datetime({
598
- label: "Created At",
599
- defaultValue: "NOW()",
600
- readonly: true
601
- }),
602
- updated_at: Field.datetime({
603
- label: "Updated At",
604
- defaultValue: "NOW()",
605
- readonly: true
606
- }),
607
- email: Field.email({
608
- label: "Email",
609
- required: true,
610
- searchable: true
611
- }),
612
- email_verified: Field.boolean({
613
- label: "Email Verified",
614
- defaultValue: false
615
- }),
616
- name: Field.text({
617
- label: "Name",
618
- required: true,
619
- searchable: true,
620
- maxLength: 255
621
- }),
622
- image: Field.url({
623
- label: "Profile Image",
624
- required: false
625
- })
626
- },
627
- indexes: [
628
- { fields: ["email"], unique: true },
629
- { fields: ["created_at"], unique: false }
630
- ],
631
- enable: {
632
- trackHistory: true,
633
- searchable: true,
634
- apiEnabled: true,
635
- apiMethods: ["get", "list", "create", "update", "delete"],
636
- trash: true,
637
- mru: true
638
- },
639
- validations: [
640
- {
641
- name: "email_unique",
642
- type: "unique",
643
- severity: "error",
644
- message: "Email must be unique",
645
- fields: ["email"],
646
- caseSensitive: false
647
- }
648
- ]
649
- });
650
-
651
- // src/objects/sys-session.object.ts
652
- import { ObjectSchema as ObjectSchema2, Field as Field2 } from "@objectstack/spec/data";
653
- var SysSession = ObjectSchema2.create({
654
- namespace: "sys",
655
- name: "session",
656
- label: "Session",
657
- pluralLabel: "Sessions",
658
- icon: "key",
659
- isSystem: true,
660
- description: "Active user sessions",
661
- titleFormat: "Session {token}",
662
- compactLayout: ["user_id", "expires_at", "ip_address"],
663
- fields: {
664
- id: Field2.text({
665
- label: "Session ID",
666
- required: true,
667
- readonly: true
668
- }),
669
- created_at: Field2.datetime({
670
- label: "Created At",
671
- defaultValue: "NOW()",
672
- readonly: true
673
- }),
674
- updated_at: Field2.datetime({
675
- label: "Updated At",
676
- defaultValue: "NOW()",
677
- readonly: true
678
- }),
679
- user_id: Field2.text({
680
- label: "User ID",
681
- required: true
682
- }),
683
- expires_at: Field2.datetime({
684
- label: "Expires At",
685
- required: true
686
- }),
687
- token: Field2.text({
688
- label: "Session Token",
689
- required: true
690
- }),
691
- ip_address: Field2.text({
692
- label: "IP Address",
693
- required: false,
694
- maxLength: 45
695
- // Support IPv6
696
- }),
697
- user_agent: Field2.textarea({
698
- label: "User Agent",
699
- required: false
700
- })
701
- },
702
- indexes: [
703
- { fields: ["token"], unique: true },
704
- { fields: ["user_id"], unique: false },
705
- { fields: ["expires_at"], unique: false }
706
- ],
707
- enable: {
708
- trackHistory: false,
709
- searchable: false,
710
- apiEnabled: true,
711
- apiMethods: ["get", "list", "create", "delete"],
712
- trash: false,
713
- mru: false
714
- }
715
- });
716
-
717
- // src/objects/sys-account.object.ts
718
- import { ObjectSchema as ObjectSchema3, Field as Field3 } from "@objectstack/spec/data";
719
- var SysAccount = ObjectSchema3.create({
720
- namespace: "sys",
721
- name: "account",
722
- label: "Account",
723
- pluralLabel: "Accounts",
724
- icon: "link",
725
- isSystem: true,
726
- description: "OAuth and authentication provider accounts",
727
- titleFormat: "{provider_id} - {account_id}",
728
- compactLayout: ["provider_id", "user_id", "account_id"],
729
- fields: {
730
- id: Field3.text({
731
- label: "Account ID",
732
- required: true,
733
- readonly: true
734
- }),
735
- created_at: Field3.datetime({
736
- label: "Created At",
737
- defaultValue: "NOW()",
738
- readonly: true
739
- }),
740
- updated_at: Field3.datetime({
741
- label: "Updated At",
742
- defaultValue: "NOW()",
743
- readonly: true
744
- }),
745
- provider_id: Field3.text({
746
- label: "Provider ID",
747
- required: true,
748
- description: "OAuth provider identifier (google, github, etc.)"
749
- }),
750
- account_id: Field3.text({
751
- label: "Provider Account ID",
752
- required: true,
753
- description: "User's ID in the provider's system"
754
- }),
755
- user_id: Field3.text({
756
- label: "User ID",
757
- required: true,
758
- description: "Link to user table"
759
- }),
760
- access_token: Field3.textarea({
761
- label: "Access Token",
762
- required: false
763
- }),
764
- refresh_token: Field3.textarea({
765
- label: "Refresh Token",
766
- required: false
767
- }),
768
- id_token: Field3.textarea({
769
- label: "ID Token",
770
- required: false
771
- }),
772
- access_token_expires_at: Field3.datetime({
773
- label: "Access Token Expires At",
774
- required: false
775
- }),
776
- refresh_token_expires_at: Field3.datetime({
777
- label: "Refresh Token Expires At",
778
- required: false
779
- }),
780
- scope: Field3.text({
781
- label: "OAuth Scope",
782
- required: false
783
- }),
784
- password: Field3.text({
785
- label: "Password Hash",
786
- required: false,
787
- description: "Hashed password for email/password provider"
788
- })
789
- },
790
- indexes: [
791
- { fields: ["user_id"], unique: false },
792
- { fields: ["provider_id", "account_id"], unique: true }
793
- ],
794
- enable: {
795
- trackHistory: false,
796
- searchable: false,
797
- apiEnabled: true,
798
- apiMethods: ["get", "list", "create", "update", "delete"],
799
- trash: true,
800
- mru: false
801
- }
802
- });
803
-
804
- // src/objects/sys-verification.object.ts
805
- import { ObjectSchema as ObjectSchema4, Field as Field4 } from "@objectstack/spec/data";
806
- var SysVerification = ObjectSchema4.create({
807
- namespace: "sys",
808
- name: "verification",
809
- label: "Verification",
810
- pluralLabel: "Verifications",
811
- icon: "shield-check",
812
- isSystem: true,
813
- description: "Email and phone verification tokens",
814
- titleFormat: "Verification for {identifier}",
815
- compactLayout: ["identifier", "expires_at", "created_at"],
816
- fields: {
817
- id: Field4.text({
818
- label: "Verification ID",
819
- required: true,
820
- readonly: true
821
- }),
822
- created_at: Field4.datetime({
823
- label: "Created At",
824
- defaultValue: "NOW()",
825
- readonly: true
826
- }),
827
- updated_at: Field4.datetime({
828
- label: "Updated At",
829
- defaultValue: "NOW()",
830
- readonly: true
831
- }),
832
- value: Field4.text({
833
- label: "Verification Token",
834
- required: true,
835
- description: "Token or code for verification"
836
- }),
837
- expires_at: Field4.datetime({
838
- label: "Expires At",
839
- required: true
840
- }),
841
- identifier: Field4.text({
842
- label: "Identifier",
843
- required: true,
844
- description: "Email address or phone number"
845
- })
846
- },
847
- indexes: [
848
- { fields: ["value"], unique: true },
849
- { fields: ["identifier"], unique: false },
850
- { fields: ["expires_at"], unique: false }
851
- ],
852
- enable: {
853
- trackHistory: false,
854
- searchable: false,
855
- apiEnabled: true,
856
- apiMethods: ["get", "create", "delete"],
857
- trash: false,
858
- mru: false
859
- }
860
- });
861
-
862
- // src/objects/sys-organization.object.ts
863
- import { ObjectSchema as ObjectSchema5, Field as Field5 } from "@objectstack/spec/data";
864
- var SysOrganization = ObjectSchema5.create({
865
- namespace: "sys",
866
- name: "organization",
867
- label: "Organization",
868
- pluralLabel: "Organizations",
869
- icon: "building-2",
870
- isSystem: true,
871
- description: "Organizations for multi-tenant grouping",
872
- titleFormat: "{name}",
873
- compactLayout: ["name", "slug", "created_at"],
874
- fields: {
875
- id: Field5.text({
876
- label: "Organization ID",
877
- required: true,
878
- readonly: true
879
- }),
880
- created_at: Field5.datetime({
881
- label: "Created At",
882
- defaultValue: "NOW()",
883
- readonly: true
884
- }),
885
- updated_at: Field5.datetime({
886
- label: "Updated At",
887
- defaultValue: "NOW()",
888
- readonly: true
889
- }),
890
- name: Field5.text({
891
- label: "Name",
892
- required: true,
893
- searchable: true,
894
- maxLength: 255
895
- }),
896
- slug: Field5.text({
897
- label: "Slug",
898
- required: false,
899
- maxLength: 255,
900
- description: "URL-friendly identifier"
901
- }),
902
- logo: Field5.url({
903
- label: "Logo",
904
- required: false
905
- }),
906
- metadata: Field5.textarea({
907
- label: "Metadata",
908
- required: false,
909
- description: "JSON-serialized organization metadata"
910
- })
911
- },
912
- indexes: [
913
- { fields: ["slug"], unique: true },
914
- { fields: ["name"] }
915
- ],
916
- enable: {
917
- trackHistory: true,
918
- searchable: true,
919
- apiEnabled: true,
920
- apiMethods: ["get", "list", "create", "update", "delete"],
921
- trash: true,
922
- mru: true
923
- }
924
- });
925
-
926
- // src/objects/sys-member.object.ts
927
- import { ObjectSchema as ObjectSchema6, Field as Field6 } from "@objectstack/spec/data";
928
- var SysMember = ObjectSchema6.create({
929
- namespace: "sys",
930
- name: "member",
931
- label: "Member",
932
- pluralLabel: "Members",
933
- icon: "user-check",
934
- isSystem: true,
935
- description: "Organization membership records",
936
- titleFormat: "{user_id} in {organization_id}",
937
- compactLayout: ["user_id", "organization_id", "role"],
938
- fields: {
939
- id: Field6.text({
940
- label: "Member ID",
941
- required: true,
942
- readonly: true
943
- }),
944
- created_at: Field6.datetime({
945
- label: "Created At",
946
- defaultValue: "NOW()",
947
- readonly: true
948
- }),
949
- organization_id: Field6.text({
950
- label: "Organization ID",
951
- required: true
952
- }),
953
- user_id: Field6.text({
954
- label: "User ID",
955
- required: true
956
- }),
957
- role: Field6.text({
958
- label: "Role",
959
- required: false,
960
- description: "Member role within the organization (e.g. admin, member)",
961
- maxLength: 100
962
- })
963
- },
964
- indexes: [
965
- { fields: ["organization_id", "user_id"], unique: true },
966
- { fields: ["user_id"] }
967
- ],
968
- enable: {
969
- trackHistory: true,
970
- searchable: false,
971
- apiEnabled: true,
972
- apiMethods: ["get", "list", "create", "update", "delete"],
973
- trash: false,
974
- mru: false
975
- }
976
- });
977
-
978
- // src/objects/sys-invitation.object.ts
979
- import { ObjectSchema as ObjectSchema7, Field as Field7 } from "@objectstack/spec/data";
980
- var SysInvitation = ObjectSchema7.create({
981
- namespace: "sys",
982
- name: "invitation",
983
- label: "Invitation",
984
- pluralLabel: "Invitations",
985
- icon: "mail",
986
- isSystem: true,
987
- description: "Organization invitations for user onboarding",
988
- titleFormat: "Invitation to {organization_id}",
989
- compactLayout: ["email", "organization_id", "status"],
990
- fields: {
991
- id: Field7.text({
992
- label: "Invitation ID",
993
- required: true,
994
- readonly: true
995
- }),
996
- created_at: Field7.datetime({
997
- label: "Created At",
998
- defaultValue: "NOW()",
999
- readonly: true
1000
- }),
1001
- organization_id: Field7.text({
1002
- label: "Organization ID",
1003
- required: true
1004
- }),
1005
- email: Field7.email({
1006
- label: "Email",
1007
- required: true,
1008
- description: "Email address of the invited user"
1009
- }),
1010
- role: Field7.text({
1011
- label: "Role",
1012
- required: false,
1013
- maxLength: 100,
1014
- description: "Role to assign upon acceptance"
1015
- }),
1016
- status: Field7.select(["pending", "accepted", "rejected", "expired", "canceled"], {
1017
- label: "Status",
1018
- required: true,
1019
- defaultValue: "pending"
1020
- }),
1021
- inviter_id: Field7.text({
1022
- label: "Inviter ID",
1023
- required: true,
1024
- description: "User ID of the person who sent the invitation"
1025
- }),
1026
- expires_at: Field7.datetime({
1027
- label: "Expires At",
1028
- required: true
1029
- }),
1030
- team_id: Field7.text({
1031
- label: "Team ID",
1032
- required: false,
1033
- description: "Optional team to assign upon acceptance"
1034
- })
1035
- },
1036
- indexes: [
1037
- { fields: ["organization_id"] },
1038
- { fields: ["email"] },
1039
- { fields: ["expires_at"] }
1040
- ],
1041
- enable: {
1042
- trackHistory: true,
1043
- searchable: false,
1044
- apiEnabled: true,
1045
- apiMethods: ["get", "list", "create", "update", "delete"],
1046
- trash: false,
1047
- mru: false
1048
- }
1049
- });
1050
-
1051
- // src/objects/sys-team.object.ts
1052
- import { ObjectSchema as ObjectSchema8, Field as Field8 } from "@objectstack/spec/data";
1053
- var SysTeam = ObjectSchema8.create({
1054
- namespace: "sys",
1055
- name: "team",
1056
- label: "Team",
1057
- pluralLabel: "Teams",
1058
- icon: "users",
1059
- isSystem: true,
1060
- description: "Teams within organizations for fine-grained grouping",
1061
- titleFormat: "{name}",
1062
- compactLayout: ["name", "organization_id", "created_at"],
1063
- fields: {
1064
- id: Field8.text({
1065
- label: "Team ID",
1066
- required: true,
1067
- readonly: true
1068
- }),
1069
- created_at: Field8.datetime({
1070
- label: "Created At",
1071
- defaultValue: "NOW()",
1072
- readonly: true
1073
- }),
1074
- updated_at: Field8.datetime({
1075
- label: "Updated At",
1076
- defaultValue: "NOW()",
1077
- readonly: true
1078
- }),
1079
- name: Field8.text({
1080
- label: "Name",
1081
- required: true,
1082
- searchable: true,
1083
- maxLength: 255
1084
- }),
1085
- organization_id: Field8.text({
1086
- label: "Organization ID",
1087
- required: true
1088
- })
1089
- },
1090
- indexes: [
1091
- { fields: ["organization_id"] },
1092
- { fields: ["name", "organization_id"], unique: true }
1093
- ],
1094
- enable: {
1095
- trackHistory: true,
1096
- searchable: true,
1097
- apiEnabled: true,
1098
- apiMethods: ["get", "list", "create", "update", "delete"],
1099
- trash: true,
1100
- mru: false
1101
- }
1102
- });
1103
-
1104
- // src/objects/sys-team-member.object.ts
1105
- import { ObjectSchema as ObjectSchema9, Field as Field9 } from "@objectstack/spec/data";
1106
- var SysTeamMember = ObjectSchema9.create({
1107
- namespace: "sys",
1108
- name: "team_member",
1109
- label: "Team Member",
1110
- pluralLabel: "Team Members",
1111
- icon: "user-plus",
1112
- isSystem: true,
1113
- description: "Team membership records linking users to teams",
1114
- titleFormat: "{user_id} in {team_id}",
1115
- compactLayout: ["user_id", "team_id", "created_at"],
1116
- fields: {
1117
- id: Field9.text({
1118
- label: "Team Member ID",
1119
- required: true,
1120
- readonly: true
1121
- }),
1122
- created_at: Field9.datetime({
1123
- label: "Created At",
1124
- defaultValue: "NOW()",
1125
- readonly: true
1126
- }),
1127
- team_id: Field9.text({
1128
- label: "Team ID",
1129
- required: true
1130
- }),
1131
- user_id: Field9.text({
1132
- label: "User ID",
1133
- required: true
1134
- })
1135
- },
1136
- indexes: [
1137
- { fields: ["team_id", "user_id"], unique: true },
1138
- { fields: ["user_id"] }
1139
- ],
1140
- enable: {
1141
- trackHistory: true,
1142
- searchable: false,
1143
- apiEnabled: true,
1144
- apiMethods: ["get", "list", "create", "delete"],
1145
- trash: false,
1146
- mru: false
1147
- }
1148
- });
1149
-
1150
- // src/objects/sys-api-key.object.ts
1151
- import { ObjectSchema as ObjectSchema10, Field as Field10 } from "@objectstack/spec/data";
1152
- var SysApiKey = ObjectSchema10.create({
1153
- namespace: "sys",
1154
- name: "api_key",
1155
- label: "API Key",
1156
- pluralLabel: "API Keys",
1157
- icon: "key-round",
1158
- isSystem: true,
1159
- description: "API keys for programmatic access",
1160
- titleFormat: "{name}",
1161
- compactLayout: ["name", "user_id", "expires_at"],
1162
- fields: {
1163
- id: Field10.text({
1164
- label: "API Key ID",
1165
- required: true,
1166
- readonly: true
1167
- }),
1168
- created_at: Field10.datetime({
1169
- label: "Created At",
1170
- defaultValue: "NOW()",
1171
- readonly: true
1172
- }),
1173
- updated_at: Field10.datetime({
1174
- label: "Updated At",
1175
- defaultValue: "NOW()",
1176
- readonly: true
1177
- }),
1178
- name: Field10.text({
1179
- label: "Name",
1180
- required: true,
1181
- maxLength: 255,
1182
- description: "Human-readable label for the API key"
1183
- }),
1184
- key: Field10.text({
1185
- label: "Key",
1186
- required: true,
1187
- description: "Hashed API key value"
1188
- }),
1189
- prefix: Field10.text({
1190
- label: "Prefix",
1191
- required: false,
1192
- maxLength: 16,
1193
- description: 'Visible prefix for identifying the key (e.g., "osk_")'
1194
- }),
1195
- user_id: Field10.text({
1196
- label: "User ID",
1197
- required: true,
1198
- description: "Owner user of this API key"
1199
- }),
1200
- scopes: Field10.textarea({
1201
- label: "Scopes",
1202
- required: false,
1203
- description: "JSON array of permission scopes"
1204
- }),
1205
- expires_at: Field10.datetime({
1206
- label: "Expires At",
1207
- required: false
1208
- }),
1209
- last_used_at: Field10.datetime({
1210
- label: "Last Used At",
1211
- required: false
1212
- }),
1213
- revoked: Field10.boolean({
1214
- label: "Revoked",
1215
- defaultValue: false
1216
- })
1217
- },
1218
- indexes: [
1219
- { fields: ["key"], unique: true },
1220
- { fields: ["user_id"] },
1221
- { fields: ["prefix"] }
1222
- ],
1223
- enable: {
1224
- trackHistory: true,
1225
- searchable: false,
1226
- apiEnabled: true,
1227
- apiMethods: ["get", "list", "create", "update", "delete"],
1228
- trash: false,
1229
- mru: false
1230
- }
1231
- });
1232
-
1233
- // src/objects/sys-two-factor.object.ts
1234
- import { ObjectSchema as ObjectSchema11, Field as Field11 } from "@objectstack/spec/data";
1235
- var SysTwoFactor = ObjectSchema11.create({
1236
- namespace: "sys",
1237
- name: "two_factor",
1238
- label: "Two Factor",
1239
- pluralLabel: "Two Factor Credentials",
1240
- icon: "smartphone",
1241
- isSystem: true,
1242
- description: "Two-factor authentication credentials",
1243
- titleFormat: "Two-factor for {user_id}",
1244
- compactLayout: ["user_id", "created_at"],
1245
- fields: {
1246
- id: Field11.text({
1247
- label: "Two Factor ID",
1248
- required: true,
1249
- readonly: true
1250
- }),
1251
- created_at: Field11.datetime({
1252
- label: "Created At",
1253
- defaultValue: "NOW()",
1254
- readonly: true
1255
- }),
1256
- updated_at: Field11.datetime({
1257
- label: "Updated At",
1258
- defaultValue: "NOW()",
1259
- readonly: true
1260
- }),
1261
- user_id: Field11.text({
1262
- label: "User ID",
1263
- required: true
1264
- }),
1265
- secret: Field11.text({
1266
- label: "Secret",
1267
- required: true,
1268
- description: "TOTP secret key"
1269
- }),
1270
- backup_codes: Field11.textarea({
1271
- label: "Backup Codes",
1272
- required: false,
1273
- description: "JSON-serialized backup recovery codes"
1274
- })
1275
- },
1276
- indexes: [
1277
- { fields: ["user_id"], unique: true }
1278
- ],
1279
- enable: {
1280
- trackHistory: false,
1281
- searchable: false,
1282
- apiEnabled: true,
1283
- apiMethods: ["get", "create", "update", "delete"],
1284
- trash: false,
1285
- mru: false
772
+ /**
773
+ * Returns the data engine wired into this auth manager. Used by route
774
+ * handlers (e.g. bootstrap-status) that need to query identity tables
775
+ * directly without going through better-auth.
776
+ */
777
+ getDataEngine() {
778
+ return this.config.dataEngine;
1286
779
  }
1287
- });
780
+ };
1288
781
 
1289
- // src/objects/sys-user-preference.object.ts
1290
- import { ObjectSchema as ObjectSchema12, Field as Field12 } from "@objectstack/spec/data";
1291
- var SysUserPreference = ObjectSchema12.create({
782
+ // src/manifest.ts
783
+ import {
784
+ SysAccount,
785
+ SysApiKey,
786
+ SysDeviceCode,
787
+ SysInvitation,
788
+ SysMember,
789
+ SysJwks,
790
+ SysOauthAccessToken,
791
+ SysOauthApplication,
792
+ SysOauthConsent,
793
+ SysOauthRefreshToken,
794
+ SysOrganization,
795
+ SysSession,
796
+ SysTeam,
797
+ SysTeamMember,
798
+ SysTwoFactor,
799
+ SysUser,
800
+ SysUserPreference,
801
+ SysVerification
802
+ } from "@objectstack/platform-objects/identity";
803
+ var AUTH_PLUGIN_ID = "com.objectstack.plugin-auth";
804
+ var AUTH_PLUGIN_VERSION = "3.0.1";
805
+ var authIdentityObjects = [
806
+ SysUser,
807
+ SysSession,
808
+ SysAccount,
809
+ SysVerification,
810
+ SysOrganization,
811
+ SysMember,
812
+ SysInvitation,
813
+ SysTeam,
814
+ SysTeamMember,
815
+ SysApiKey,
816
+ SysTwoFactor,
817
+ SysUserPreference,
818
+ SysOauthApplication,
819
+ SysOauthAccessToken,
820
+ SysOauthRefreshToken,
821
+ SysOauthConsent,
822
+ SysJwks,
823
+ SysDeviceCode
824
+ ];
825
+ var authPluginManifestHeader = {
826
+ id: AUTH_PLUGIN_ID,
1292
827
  namespace: "sys",
1293
- name: "user_preference",
1294
- label: "User Preference",
1295
- pluralLabel: "User Preferences",
1296
- icon: "settings",
1297
- isSystem: true,
1298
- description: "Per-user key-value preferences (theme, locale, etc.)",
1299
- titleFormat: "{key}",
1300
- compactLayout: ["user_id", "key"],
1301
- fields: {
1302
- id: Field12.text({
1303
- label: "Preference ID",
1304
- required: true,
1305
- readonly: true
1306
- }),
1307
- created_at: Field12.datetime({
1308
- label: "Created At",
1309
- defaultValue: "NOW()",
1310
- readonly: true
1311
- }),
1312
- updated_at: Field12.datetime({
1313
- label: "Updated At",
1314
- defaultValue: "NOW()",
1315
- readonly: true
1316
- }),
1317
- user_id: Field12.text({
1318
- label: "User ID",
1319
- required: true,
1320
- maxLength: 255,
1321
- description: "Owner user of this preference"
1322
- }),
1323
- key: Field12.text({
1324
- label: "Key",
1325
- required: true,
1326
- maxLength: 255,
1327
- description: "Preference key (e.g., theme, locale, plugin.ai.auto_save)"
1328
- }),
1329
- value: Field12.json({
1330
- label: "Value",
1331
- description: "Preference value (any JSON-serializable type)"
1332
- })
1333
- },
1334
- indexes: [
1335
- { fields: ["user_id", "key"], unique: true },
1336
- { fields: ["user_id"], unique: false }
1337
- ],
1338
- enable: {
1339
- trackHistory: false,
1340
- searchable: false,
1341
- apiEnabled: true,
1342
- apiMethods: ["get", "list", "create", "update", "delete"],
1343
- trash: false,
1344
- mru: false
1345
- }
1346
- });
828
+ version: AUTH_PLUGIN_VERSION,
829
+ type: "plugin",
830
+ scope: "system",
831
+ defaultDatasource: "cloud",
832
+ name: "Authentication & Identity Plugin",
833
+ description: "Core authentication objects for ObjectStack (User, Session, Account, Verification)"
834
+ };
1347
835
 
1348
836
  // src/auth-plugin.ts
1349
837
  var AuthPlugin = class {
@@ -1374,43 +862,27 @@ var AuthPlugin = class {
1374
862
  });
1375
863
  ctx.registerService("auth", this.authManager);
1376
864
  ctx.getService("manifest").register({
1377
- id: "com.objectstack.system",
1378
- name: "System",
1379
- version: "1.0.0",
1380
- type: "plugin",
1381
- namespace: "sys",
1382
- objects: [
1383
- SysUser,
1384
- SysSession,
1385
- SysAccount,
1386
- SysVerification,
1387
- SysOrganization,
1388
- SysMember,
1389
- SysInvitation,
1390
- SysTeam,
1391
- SysTeamMember,
1392
- SysApiKey,
1393
- SysTwoFactor,
1394
- SysUserPreference
1395
- ]
865
+ ...authPluginManifestHeader,
866
+ objects: authIdentityObjects,
867
+ // The platform Setup App is a static metadata artifact (lives in
868
+ // @objectstack/platform-objects/apps). plugin-auth is the natural
869
+ // owner of its registration since it loads first among the trio
870
+ // (auth + security + audit) that supplies the underlying objects.
871
+ apps: [SETUP_APP],
872
+ // Curated list views and dashboards consumed by the Setup App's
873
+ // navigation entries. The manifest service does NOT auto-discover
874
+ // these from the app definition — they must be registered as
875
+ // explicit top-level arrays per ObjectStackDefinitionSchema.
876
+ views: [
877
+ UsersView,
878
+ OrganizationsView,
879
+ RolesView,
880
+ SessionsView,
881
+ AuditLogsView,
882
+ PackageInstallationsView
883
+ ],
884
+ dashboards: [SystemOverviewDashboard, SecurityOverviewDashboard]
1396
885
  });
1397
- try {
1398
- const setupNav = ctx.getService("setupNav");
1399
- if (setupNav) {
1400
- setupNav.contribute({
1401
- areaId: "area_administration",
1402
- items: [
1403
- { id: "nav_users", type: "object", label: "Users", objectName: "user", icon: "users", order: 10 },
1404
- { id: "nav_organizations", type: "object", label: "Organizations", objectName: "organization", icon: "building-2", order: 20 },
1405
- { id: "nav_teams", type: "object", label: "Teams", objectName: "team", icon: "users-round", order: 30 },
1406
- { id: "nav_api_keys", type: "object", label: "API Keys", objectName: "api_key", icon: "key", order: 40 },
1407
- { id: "nav_sessions", type: "object", label: "Sessions", objectName: "session", icon: "monitor", order: 50 }
1408
- ]
1409
- });
1410
- ctx.logger.info("Auth navigation items contributed to Setup App");
1411
- }
1412
- } catch {
1413
- }
1414
886
  ctx.logger.info("Auth Plugin initialized successfully");
1415
887
  }
1416
888
  async start(ctx) {
@@ -1433,7 +905,8 @@ var AuthPlugin = class {
1433
905
  const configuredUrl = this.options.baseUrl || "http://localhost:3000";
1434
906
  const configuredOrigin = new URL(configuredUrl).origin;
1435
907
  const actualUrl = `http://localhost:${actualPort}`;
1436
- if (configuredOrigin !== actualUrl) {
908
+ const configuredIsLocalhost = configuredOrigin.startsWith("http://localhost");
909
+ if (configuredIsLocalhost && configuredOrigin !== actualUrl) {
1437
910
  this.authManager.setRuntimeBaseUrl(actualUrl);
1438
911
  ctx.logger.info(
1439
912
  `Auth baseUrl auto-updated to ${actualUrl} (configured: ${configuredUrl})`
@@ -1491,23 +964,26 @@ var AuthPlugin = class {
1491
964
  );
1492
965
  }
1493
966
  const rawApp = httpServer.getRawApp();
1494
- rawApp.get(`${basePath}/config`, async (c) => {
967
+ rawApp.get(`${basePath}/config`, (c) => {
1495
968
  try {
1496
969
  const config = this.authManager.getPublicConfig();
1497
- return c.json({
1498
- success: true,
1499
- data: config
1500
- });
970
+ return c.json({ success: true, data: config });
1501
971
  } catch (error) {
1502
972
  const err = error instanceof Error ? error : new Error(String(error));
1503
- ctx.logger.error("Auth config error:", err);
1504
- return c.json({
1505
- success: false,
1506
- error: {
1507
- code: "auth_config_error",
1508
- message: err.message
1509
- }
1510
- }, 500);
973
+ return c.json({ success: false, error: { code: "auth_config_error", message: err.message } }, 500);
974
+ }
975
+ });
976
+ rawApp.get(`${basePath}/bootstrap-status`, async (c) => {
977
+ try {
978
+ const dataEngine = this.authManager.getDataEngine();
979
+ if (!dataEngine) {
980
+ return c.json({ hasOwner: true });
981
+ }
982
+ const count = await dataEngine.count("sys_user", {});
983
+ return c.json({ hasOwner: (count ?? 0) > 0 });
984
+ } catch (error) {
985
+ ctx.logger.warn("[AuthPlugin] bootstrap-status check failed; assuming bootstrapped", error);
986
+ return c.json({ hasOwner: true });
1511
987
  }
1512
988
  });
1513
989
  rawApp.all(`${basePath}/*`, async (c) => {
@@ -1537,14 +1013,43 @@ var AuthPlugin = class {
1537
1013
  );
1538
1014
  }
1539
1015
  });
1016
+ if (this.options.plugins?.oidcProvider) {
1017
+ void this.registerOidcDiscoveryRoutes(rawApp, ctx).catch((error) => {
1018
+ ctx.logger.error("Failed to register OIDC discovery routes", error);
1019
+ });
1020
+ }
1540
1021
  ctx.logger.info(`Auth routes registered: All requests under ${basePath}/* forwarded to better-auth`);
1541
1022
  }
1023
+ /**
1024
+ * Mount the OIDC / OAuth 2.0 well-known discovery documents at the root
1025
+ * URL. Required by RFC 8414 §3 and OpenID Connect Discovery 1.0 §4 — the
1026
+ * documents must live at `/.well-known/{oauth-authorization-server,openid-configuration}`
1027
+ * relative to the issuer, not under the auth basePath.
1028
+ */
1029
+ async registerOidcDiscoveryRoutes(rawApp, ctx) {
1030
+ const auth = await this.authManager.getAuthInstance();
1031
+ const { oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata } = await import("@better-auth/oauth-provider");
1032
+ const authServerHandler = oauthProviderAuthServerMetadata(auth);
1033
+ const openidConfigHandler = oauthProviderOpenIdConfigMetadata(auth);
1034
+ rawApp.get("/.well-known/oauth-authorization-server", (c) => authServerHandler(c.req.raw));
1035
+ rawApp.get("/.well-known/openid-configuration", (c) => openidConfigHandler(c.req.raw));
1036
+ ctx.logger.info(
1037
+ "OIDC discovery endpoints mounted at /.well-known/{oauth-authorization-server,openid-configuration}"
1038
+ );
1039
+ }
1542
1040
  };
1543
1041
  export {
1544
1042
  AUTH_ACCOUNT_CONFIG,
1043
+ AUTH_DEVICE_CODE_SCHEMA,
1545
1044
  AUTH_INVITATION_SCHEMA,
1045
+ AUTH_JWKS_SCHEMA,
1546
1046
  AUTH_MEMBER_SCHEMA,
1547
1047
  AUTH_MODEL_TO_PROTOCOL,
1048
+ AUTH_OAUTH_ACCESS_TOKEN_SCHEMA,
1049
+ AUTH_OAUTH_APPLICATION_SCHEMA,
1050
+ AUTH_OAUTH_CLIENT_SCHEMA,
1051
+ AUTH_OAUTH_CONSENT_SCHEMA,
1052
+ AUTH_OAUTH_REFRESH_TOKEN_SCHEMA,
1548
1053
  AUTH_ORGANIZATION_SCHEMA,
1549
1054
  AUTH_ORG_SESSION_FIELDS,
1550
1055
  AUTH_SESSION_CONFIG,
@@ -1554,24 +1059,12 @@ export {
1554
1059
  AUTH_TWO_FACTOR_USER_FIELDS,
1555
1060
  AUTH_USER_CONFIG,
1556
1061
  AUTH_VERIFICATION_CONFIG,
1557
- SysAccount as AuthAccount,
1558
1062
  AuthManager,
1559
1063
  AuthPlugin,
1560
- SysSession as AuthSession,
1561
- SysUser as AuthUser,
1562
- SysVerification as AuthVerification,
1563
- SysAccount,
1564
- SysApiKey,
1565
- SysInvitation,
1566
- SysMember,
1567
- SysOrganization,
1568
- SysSession,
1569
- SysTeam,
1570
- SysTeamMember,
1571
- SysTwoFactor,
1572
- SysUser,
1573
- SysUserPreference,
1574
- SysVerification,
1064
+ buildDeviceAuthorizationPluginSchema,
1065
+ buildJwtPluginSchema,
1066
+ buildOauthProviderPluginSchema,
1067
+ buildOidcProviderPluginSchema,
1575
1068
  buildOrganizationPluginSchema,
1576
1069
  buildTwoFactorPluginSchema,
1577
1070
  createObjectQLAdapter,