@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.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,9 +483,22 @@ 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
- ...this.config.trustedOrigins?.length ? { trustedOrigins: this.config.trustedOrigins } : {},
488
+ // Auto-includes origins from CORS_ORIGIN env var so CORS and CSRF stay in sync.
489
+ ...(() => {
490
+ const origins = [...this.config.trustedOrigins || []];
491
+ const corsOrigin = process.env.CORS_ORIGIN;
492
+ if (corsOrigin && corsOrigin !== "*") {
493
+ corsOrigin.split(",").map((s) => s.trim()).filter(Boolean).forEach((o) => {
494
+ if (!origins.includes(o)) origins.push(o);
495
+ });
496
+ }
497
+ if (!origins.length && (!corsOrigin || corsOrigin === "*")) {
498
+ origins.push("http://localhost:*");
499
+ }
500
+ return origins.length ? { trustedOrigins: origins } : {};
501
+ })(),
376
502
  // Advanced options (cross-subdomain cookies, secure cookies, CSRF, etc.)
377
503
  ...this.config.advanced ? {
378
504
  advanced: {
@@ -392,20 +518,51 @@ var AuthManager = class {
392
518
  * a `schema` option containing the appropriate snake_case field mappings,
393
519
  * so that `createAdapterFactory` transforms them automatically.
394
520
  */
395
- buildPluginList() {
396
- const pluginConfig = this.config.plugins;
521
+ async buildPluginList() {
522
+ const pluginConfig = this.config.plugins ?? {};
397
523
  const plugins = [];
398
- 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");
399
536
  plugins.push(organization({
400
- 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
+ }
401
556
  }));
402
557
  }
403
- if (pluginConfig?.twoFactor) {
558
+ if (enabled.twoFactor) {
559
+ const { twoFactor } = await import("better-auth/plugins/two-factor");
404
560
  plugins.push(twoFactor({
405
561
  schema: buildTwoFactorPluginSchema()
406
562
  }));
407
563
  }
408
- if (pluginConfig?.magicLink) {
564
+ if (enabled.magicLink) {
565
+ const { magicLink } = await import("better-auth/plugins/magic-link");
409
566
  plugins.push(magicLink({
410
567
  sendMagicLink: async ({ email, url }) => {
411
568
  console.warn(
@@ -414,6 +571,43 @@ var AuthManager = class {
414
571
  }
415
572
  }));
416
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
+ }
417
611
  return plugins;
418
612
  }
419
613
  /**
@@ -489,7 +683,7 @@ var AuthManager = class {
489
683
  * @returns Web standard Response object
490
684
  */
491
685
  async handleRequest(request) {
492
- const auth = this.getOrCreateAuth();
686
+ const auth = await this.getOrCreateAuth();
493
687
  const response = await auth.handler(request);
494
688
  if (response.status >= 500) {
495
689
  try {
@@ -505,779 +699,139 @@ var AuthManager = class {
505
699
  * Get the better-auth API for programmatic access
506
700
  * Use this for server-side operations (e.g., creating users, checking sessions)
507
701
  */
508
- get api() {
509
- return this.getOrCreateAuth().api;
702
+ async getApi() {
703
+ const auth = await this.getOrCreateAuth();
704
+ return auth.api;
510
705
  }
511
- };
512
-
513
- // src/objects/sys-user.object.ts
514
- import { ObjectSchema, Field } from "@objectstack/spec/data";
515
- var SysUser = ObjectSchema.create({
516
- namespace: "sys",
517
- name: "user",
518
- label: "User",
519
- pluralLabel: "Users",
520
- icon: "user",
521
- isSystem: true,
522
- description: "User accounts for authentication",
523
- titleFormat: "{name} ({email})",
524
- compactLayout: ["name", "email", "email_verified"],
525
- fields: {
526
- id: Field.text({
527
- label: "User ID",
528
- required: true,
529
- readonly: true
530
- }),
531
- created_at: Field.datetime({
532
- label: "Created At",
533
- defaultValue: "NOW()",
534
- readonly: true
535
- }),
536
- updated_at: Field.datetime({
537
- label: "Updated At",
538
- defaultValue: "NOW()",
539
- readonly: true
540
- }),
541
- email: Field.email({
542
- label: "Email",
543
- required: true,
544
- searchable: true
545
- }),
546
- email_verified: Field.boolean({
547
- label: "Email Verified",
548
- defaultValue: false
549
- }),
550
- name: Field.text({
551
- label: "Name",
552
- required: true,
553
- searchable: true,
554
- maxLength: 255
555
- }),
556
- image: Field.url({
557
- label: "Profile Image",
558
- required: false
559
- })
560
- },
561
- indexes: [
562
- { fields: ["email"], unique: true },
563
- { fields: ["created_at"], unique: false }
564
- ],
565
- enable: {
566
- trackHistory: true,
567
- searchable: true,
568
- apiEnabled: true,
569
- apiMethods: ["get", "list", "create", "update", "delete"],
570
- trash: true,
571
- mru: true
572
- },
573
- validations: [
574
- {
575
- name: "email_unique",
576
- type: "unique",
577
- severity: "error",
578
- message: "Email must be unique",
579
- fields: ["email"],
580
- caseSensitive: false
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
+ // ---------------------------------------------------------------------------
715
+ getPublicConfig() {
716
+ const socialProviders = [];
717
+ if (this.config.socialProviders) {
718
+ for (const [id, providerConfig] of Object.entries(this.config.socialProviders)) {
719
+ if (providerConfig.enabled !== false) {
720
+ const nameMap = {
721
+ google: "Google",
722
+ github: "GitHub",
723
+ microsoft: "Microsoft",
724
+ apple: "Apple",
725
+ facebook: "Facebook",
726
+ twitter: "Twitter",
727
+ discord: "Discord",
728
+ gitlab: "GitLab",
729
+ linkedin: "LinkedIn"
730
+ };
731
+ socialProviders.push({
732
+ id,
733
+ name: nameMap[id] || id.charAt(0).toUpperCase() + id.slice(1),
734
+ enabled: true,
735
+ type: "social"
736
+ });
737
+ }
738
+ }
581
739
  }
582
- ]
583
- });
584
-
585
- // src/objects/sys-session.object.ts
586
- import { ObjectSchema as ObjectSchema2, Field as Field2 } from "@objectstack/spec/data";
587
- var SysSession = ObjectSchema2.create({
588
- namespace: "sys",
589
- name: "session",
590
- label: "Session",
591
- pluralLabel: "Sessions",
592
- icon: "key",
593
- isSystem: true,
594
- description: "Active user sessions",
595
- titleFormat: "Session {token}",
596
- compactLayout: ["user_id", "expires_at", "ip_address"],
597
- fields: {
598
- id: Field2.text({
599
- label: "Session ID",
600
- required: true,
601
- readonly: true
602
- }),
603
- created_at: Field2.datetime({
604
- label: "Created At",
605
- defaultValue: "NOW()",
606
- readonly: true
607
- }),
608
- updated_at: Field2.datetime({
609
- label: "Updated At",
610
- defaultValue: "NOW()",
611
- readonly: true
612
- }),
613
- user_id: Field2.text({
614
- label: "User ID",
615
- required: true
616
- }),
617
- expires_at: Field2.datetime({
618
- label: "Expires At",
619
- required: true
620
- }),
621
- token: Field2.text({
622
- label: "Session Token",
623
- required: true
624
- }),
625
- ip_address: Field2.text({
626
- label: "IP Address",
627
- required: false,
628
- maxLength: 45
629
- // Support IPv6
630
- }),
631
- user_agent: Field2.textarea({
632
- label: "User Agent",
633
- required: false
634
- })
635
- },
636
- indexes: [
637
- { fields: ["token"], unique: true },
638
- { fields: ["user_id"], unique: false },
639
- { fields: ["expires_at"], unique: false }
640
- ],
641
- enable: {
642
- trackHistory: false,
643
- searchable: false,
644
- apiEnabled: true,
645
- apiMethods: ["get", "list", "create", "delete"],
646
- trash: false,
647
- mru: false
648
- }
649
- });
650
-
651
- // src/objects/sys-account.object.ts
652
- import { ObjectSchema as ObjectSchema3, Field as Field3 } from "@objectstack/spec/data";
653
- var SysAccount = ObjectSchema3.create({
654
- namespace: "sys",
655
- name: "account",
656
- label: "Account",
657
- pluralLabel: "Accounts",
658
- icon: "link",
659
- isSystem: true,
660
- description: "OAuth and authentication provider accounts",
661
- titleFormat: "{provider_id} - {account_id}",
662
- compactLayout: ["provider_id", "user_id", "account_id"],
663
- fields: {
664
- id: Field3.text({
665
- label: "Account ID",
666
- required: true,
667
- readonly: true
668
- }),
669
- created_at: Field3.datetime({
670
- label: "Created At",
671
- defaultValue: "NOW()",
672
- readonly: true
673
- }),
674
- updated_at: Field3.datetime({
675
- label: "Updated At",
676
- defaultValue: "NOW()",
677
- readonly: true
678
- }),
679
- provider_id: Field3.text({
680
- label: "Provider ID",
681
- required: true,
682
- description: "OAuth provider identifier (google, github, etc.)"
683
- }),
684
- account_id: Field3.text({
685
- label: "Provider Account ID",
686
- required: true,
687
- description: "User's ID in the provider's system"
688
- }),
689
- user_id: Field3.text({
690
- label: "User ID",
691
- required: true,
692
- description: "Link to user table"
693
- }),
694
- access_token: Field3.textarea({
695
- label: "Access Token",
696
- required: false
697
- }),
698
- refresh_token: Field3.textarea({
699
- label: "Refresh Token",
700
- required: false
701
- }),
702
- id_token: Field3.textarea({
703
- label: "ID Token",
704
- required: false
705
- }),
706
- access_token_expires_at: Field3.datetime({
707
- label: "Access Token Expires At",
708
- required: false
709
- }),
710
- refresh_token_expires_at: Field3.datetime({
711
- label: "Refresh Token Expires At",
712
- required: false
713
- }),
714
- scope: Field3.text({
715
- label: "OAuth Scope",
716
- required: false
717
- }),
718
- password: Field3.text({
719
- label: "Password Hash",
720
- required: false,
721
- description: "Hashed password for email/password provider"
722
- })
723
- },
724
- indexes: [
725
- { fields: ["user_id"], unique: false },
726
- { fields: ["provider_id", "account_id"], unique: true }
727
- ],
728
- enable: {
729
- trackHistory: false,
730
- searchable: false,
731
- apiEnabled: true,
732
- apiMethods: ["get", "list", "create", "update", "delete"],
733
- trash: true,
734
- mru: false
735
- }
736
- });
737
-
738
- // src/objects/sys-verification.object.ts
739
- import { ObjectSchema as ObjectSchema4, Field as Field4 } from "@objectstack/spec/data";
740
- var SysVerification = ObjectSchema4.create({
741
- namespace: "sys",
742
- name: "verification",
743
- label: "Verification",
744
- pluralLabel: "Verifications",
745
- icon: "shield-check",
746
- isSystem: true,
747
- description: "Email and phone verification tokens",
748
- titleFormat: "Verification for {identifier}",
749
- compactLayout: ["identifier", "expires_at", "created_at"],
750
- fields: {
751
- id: Field4.text({
752
- label: "Verification ID",
753
- required: true,
754
- readonly: true
755
- }),
756
- created_at: Field4.datetime({
757
- label: "Created At",
758
- defaultValue: "NOW()",
759
- readonly: true
760
- }),
761
- updated_at: Field4.datetime({
762
- label: "Updated At",
763
- defaultValue: "NOW()",
764
- readonly: true
765
- }),
766
- value: Field4.text({
767
- label: "Verification Token",
768
- required: true,
769
- description: "Token or code for verification"
770
- }),
771
- expires_at: Field4.datetime({
772
- label: "Expires At",
773
- required: true
774
- }),
775
- identifier: Field4.text({
776
- label: "Identifier",
777
- required: true,
778
- description: "Email address or phone number"
779
- })
780
- },
781
- indexes: [
782
- { fields: ["value"], unique: true },
783
- { fields: ["identifier"], unique: false },
784
- { fields: ["expires_at"], unique: false }
785
- ],
786
- enable: {
787
- trackHistory: false,
788
- searchable: false,
789
- apiEnabled: true,
790
- apiMethods: ["get", "create", "delete"],
791
- trash: false,
792
- mru: false
793
- }
794
- });
795
-
796
- // src/objects/sys-organization.object.ts
797
- import { ObjectSchema as ObjectSchema5, Field as Field5 } from "@objectstack/spec/data";
798
- var SysOrganization = ObjectSchema5.create({
799
- namespace: "sys",
800
- name: "organization",
801
- label: "Organization",
802
- pluralLabel: "Organizations",
803
- icon: "building-2",
804
- isSystem: true,
805
- description: "Organizations for multi-tenant grouping",
806
- titleFormat: "{name}",
807
- compactLayout: ["name", "slug", "created_at"],
808
- fields: {
809
- id: Field5.text({
810
- label: "Organization ID",
811
- required: true,
812
- readonly: true
813
- }),
814
- created_at: Field5.datetime({
815
- label: "Created At",
816
- defaultValue: "NOW()",
817
- readonly: true
818
- }),
819
- updated_at: Field5.datetime({
820
- label: "Updated At",
821
- defaultValue: "NOW()",
822
- readonly: true
823
- }),
824
- name: Field5.text({
825
- label: "Name",
826
- required: true,
827
- searchable: true,
828
- maxLength: 255
829
- }),
830
- slug: Field5.text({
831
- label: "Slug",
832
- required: false,
833
- maxLength: 255,
834
- description: "URL-friendly identifier"
835
- }),
836
- logo: Field5.url({
837
- label: "Logo",
838
- required: false
839
- }),
840
- metadata: Field5.textarea({
841
- label: "Metadata",
842
- required: false,
843
- description: "JSON-serialized organization metadata"
844
- })
845
- },
846
- indexes: [
847
- { fields: ["slug"], unique: true },
848
- { fields: ["name"] }
849
- ],
850
- enable: {
851
- trackHistory: true,
852
- searchable: true,
853
- apiEnabled: true,
854
- apiMethods: ["get", "list", "create", "update", "delete"],
855
- trash: true,
856
- mru: true
857
- }
858
- });
859
-
860
- // src/objects/sys-member.object.ts
861
- import { ObjectSchema as ObjectSchema6, Field as Field6 } from "@objectstack/spec/data";
862
- var SysMember = ObjectSchema6.create({
863
- namespace: "sys",
864
- name: "member",
865
- label: "Member",
866
- pluralLabel: "Members",
867
- icon: "user-check",
868
- isSystem: true,
869
- description: "Organization membership records",
870
- titleFormat: "{user_id} in {organization_id}",
871
- compactLayout: ["user_id", "organization_id", "role"],
872
- fields: {
873
- id: Field6.text({
874
- label: "Member ID",
875
- required: true,
876
- readonly: true
877
- }),
878
- created_at: Field6.datetime({
879
- label: "Created At",
880
- defaultValue: "NOW()",
881
- readonly: true
882
- }),
883
- organization_id: Field6.text({
884
- label: "Organization ID",
885
- required: true
886
- }),
887
- user_id: Field6.text({
888
- label: "User ID",
889
- required: true
890
- }),
891
- role: Field6.text({
892
- label: "Role",
893
- required: false,
894
- description: "Member role within the organization (e.g. admin, member)",
895
- maxLength: 100
896
- })
897
- },
898
- indexes: [
899
- { fields: ["organization_id", "user_id"], unique: true },
900
- { fields: ["user_id"] }
901
- ],
902
- enable: {
903
- trackHistory: true,
904
- searchable: false,
905
- apiEnabled: true,
906
- apiMethods: ["get", "list", "create", "update", "delete"],
907
- trash: false,
908
- mru: false
909
- }
910
- });
911
-
912
- // src/objects/sys-invitation.object.ts
913
- import { ObjectSchema as ObjectSchema7, Field as Field7 } from "@objectstack/spec/data";
914
- var SysInvitation = ObjectSchema7.create({
915
- namespace: "sys",
916
- name: "invitation",
917
- label: "Invitation",
918
- pluralLabel: "Invitations",
919
- icon: "mail",
920
- isSystem: true,
921
- description: "Organization invitations for user onboarding",
922
- titleFormat: "Invitation to {organization_id}",
923
- compactLayout: ["email", "organization_id", "status"],
924
- fields: {
925
- id: Field7.text({
926
- label: "Invitation ID",
927
- required: true,
928
- readonly: true
929
- }),
930
- created_at: Field7.datetime({
931
- label: "Created At",
932
- defaultValue: "NOW()",
933
- readonly: true
934
- }),
935
- organization_id: Field7.text({
936
- label: "Organization ID",
937
- required: true
938
- }),
939
- email: Field7.email({
940
- label: "Email",
941
- required: true,
942
- description: "Email address of the invited user"
943
- }),
944
- role: Field7.text({
945
- label: "Role",
946
- required: false,
947
- maxLength: 100,
948
- description: "Role to assign upon acceptance"
949
- }),
950
- status: Field7.select(["pending", "accepted", "rejected", "expired", "canceled"], {
951
- label: "Status",
952
- required: true,
953
- defaultValue: "pending"
954
- }),
955
- inviter_id: Field7.text({
956
- label: "Inviter ID",
957
- required: true,
958
- description: "User ID of the person who sent the invitation"
959
- }),
960
- expires_at: Field7.datetime({
961
- label: "Expires At",
962
- required: true
963
- }),
964
- team_id: Field7.text({
965
- label: "Team ID",
966
- required: false,
967
- description: "Optional team to assign upon acceptance"
968
- })
969
- },
970
- indexes: [
971
- { fields: ["organization_id"] },
972
- { fields: ["email"] },
973
- { fields: ["expires_at"] }
974
- ],
975
- enable: {
976
- trackHistory: true,
977
- searchable: false,
978
- apiEnabled: true,
979
- apiMethods: ["get", "list", "create", "update", "delete"],
980
- trash: false,
981
- mru: false
982
- }
983
- });
984
-
985
- // src/objects/sys-team.object.ts
986
- import { ObjectSchema as ObjectSchema8, Field as Field8 } from "@objectstack/spec/data";
987
- var SysTeam = ObjectSchema8.create({
988
- namespace: "sys",
989
- name: "team",
990
- label: "Team",
991
- pluralLabel: "Teams",
992
- icon: "users",
993
- isSystem: true,
994
- description: "Teams within organizations for fine-grained grouping",
995
- titleFormat: "{name}",
996
- compactLayout: ["name", "organization_id", "created_at"],
997
- fields: {
998
- id: Field8.text({
999
- label: "Team ID",
1000
- required: true,
1001
- readonly: true
1002
- }),
1003
- created_at: Field8.datetime({
1004
- label: "Created At",
1005
- defaultValue: "NOW()",
1006
- readonly: true
1007
- }),
1008
- updated_at: Field8.datetime({
1009
- label: "Updated At",
1010
- defaultValue: "NOW()",
1011
- readonly: true
1012
- }),
1013
- name: Field8.text({
1014
- label: "Name",
1015
- required: true,
1016
- searchable: true,
1017
- maxLength: 255
1018
- }),
1019
- organization_id: Field8.text({
1020
- label: "Organization ID",
1021
- required: true
1022
- })
1023
- },
1024
- indexes: [
1025
- { fields: ["organization_id"] },
1026
- { fields: ["name", "organization_id"], unique: true }
1027
- ],
1028
- enable: {
1029
- trackHistory: true,
1030
- searchable: true,
1031
- apiEnabled: true,
1032
- apiMethods: ["get", "list", "create", "update", "delete"],
1033
- trash: true,
1034
- mru: false
1035
- }
1036
- });
1037
-
1038
- // src/objects/sys-team-member.object.ts
1039
- import { ObjectSchema as ObjectSchema9, Field as Field9 } from "@objectstack/spec/data";
1040
- var SysTeamMember = ObjectSchema9.create({
1041
- namespace: "sys",
1042
- name: "team_member",
1043
- label: "Team Member",
1044
- pluralLabel: "Team Members",
1045
- icon: "user-plus",
1046
- isSystem: true,
1047
- description: "Team membership records linking users to teams",
1048
- titleFormat: "{user_id} in {team_id}",
1049
- compactLayout: ["user_id", "team_id", "created_at"],
1050
- fields: {
1051
- id: Field9.text({
1052
- label: "Team Member ID",
1053
- required: true,
1054
- readonly: true
1055
- }),
1056
- created_at: Field9.datetime({
1057
- label: "Created At",
1058
- defaultValue: "NOW()",
1059
- readonly: true
1060
- }),
1061
- team_id: Field9.text({
1062
- label: "Team ID",
1063
- required: true
1064
- }),
1065
- user_id: Field9.text({
1066
- label: "User ID",
1067
- required: true
1068
- })
1069
- },
1070
- indexes: [
1071
- { fields: ["team_id", "user_id"], unique: true },
1072
- { fields: ["user_id"] }
1073
- ],
1074
- enable: {
1075
- trackHistory: true,
1076
- searchable: false,
1077
- apiEnabled: true,
1078
- apiMethods: ["get", "list", "create", "delete"],
1079
- trash: false,
1080
- mru: false
1081
- }
1082
- });
1083
-
1084
- // src/objects/sys-api-key.object.ts
1085
- import { ObjectSchema as ObjectSchema10, Field as Field10 } from "@objectstack/spec/data";
1086
- var SysApiKey = ObjectSchema10.create({
1087
- namespace: "sys",
1088
- name: "api_key",
1089
- label: "API Key",
1090
- pluralLabel: "API Keys",
1091
- icon: "key-round",
1092
- isSystem: true,
1093
- description: "API keys for programmatic access",
1094
- titleFormat: "{name}",
1095
- compactLayout: ["name", "user_id", "expires_at"],
1096
- fields: {
1097
- id: Field10.text({
1098
- label: "API Key ID",
1099
- required: true,
1100
- readonly: true
1101
- }),
1102
- created_at: Field10.datetime({
1103
- label: "Created At",
1104
- defaultValue: "NOW()",
1105
- readonly: true
1106
- }),
1107
- updated_at: Field10.datetime({
1108
- label: "Updated At",
1109
- defaultValue: "NOW()",
1110
- readonly: true
1111
- }),
1112
- name: Field10.text({
1113
- label: "Name",
1114
- required: true,
1115
- maxLength: 255,
1116
- description: "Human-readable label for the API key"
1117
- }),
1118
- key: Field10.text({
1119
- label: "Key",
1120
- required: true,
1121
- description: "Hashed API key value"
1122
- }),
1123
- prefix: Field10.text({
1124
- label: "Prefix",
1125
- required: false,
1126
- maxLength: 16,
1127
- description: 'Visible prefix for identifying the key (e.g., "osk_")'
1128
- }),
1129
- user_id: Field10.text({
1130
- label: "User ID",
1131
- required: true,
1132
- description: "Owner user of this API key"
1133
- }),
1134
- scopes: Field10.textarea({
1135
- label: "Scopes",
1136
- required: false,
1137
- description: "JSON array of permission scopes"
1138
- }),
1139
- expires_at: Field10.datetime({
1140
- label: "Expires At",
1141
- required: false
1142
- }),
1143
- last_used_at: Field10.datetime({
1144
- label: "Last Used At",
1145
- required: false
1146
- }),
1147
- revoked: Field10.boolean({
1148
- label: "Revoked",
1149
- defaultValue: false
1150
- })
1151
- },
1152
- indexes: [
1153
- { fields: ["key"], unique: true },
1154
- { fields: ["user_id"] },
1155
- { fields: ["prefix"] }
1156
- ],
1157
- enable: {
1158
- trackHistory: true,
1159
- searchable: false,
1160
- apiEnabled: true,
1161
- apiMethods: ["get", "list", "create", "update", "delete"],
1162
- trash: false,
1163
- mru: false
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
+ }
750
+ const emailPasswordConfig = this.config.emailAndPassword ?? {};
751
+ const emailPassword = {
752
+ enabled: emailPasswordConfig.enabled !== false,
753
+ // Default to true
754
+ disableSignUp: emailPasswordConfig.disableSignUp ?? false,
755
+ requireEmailVerification: emailPasswordConfig.requireEmailVerification ?? false
756
+ };
757
+ const pluginConfig = this.config.plugins ?? {};
758
+ const features = {
759
+ twoFactor: pluginConfig.twoFactor ?? false,
760
+ passkeys: pluginConfig.passkeys ?? false,
761
+ magicLink: pluginConfig.magicLink ?? false,
762
+ organization: pluginConfig.organization ?? true,
763
+ oidcProvider: pluginConfig.oidcProvider ?? false,
764
+ deviceAuthorization: pluginConfig.deviceAuthorization ?? false
765
+ };
766
+ return {
767
+ emailPassword,
768
+ socialProviders,
769
+ features
770
+ };
1164
771
  }
1165
- });
1166
-
1167
- // src/objects/sys-two-factor.object.ts
1168
- import { ObjectSchema as ObjectSchema11, Field as Field11 } from "@objectstack/spec/data";
1169
- var SysTwoFactor = ObjectSchema11.create({
1170
- namespace: "sys",
1171
- name: "two_factor",
1172
- label: "Two Factor",
1173
- pluralLabel: "Two Factor Credentials",
1174
- icon: "smartphone",
1175
- isSystem: true,
1176
- description: "Two-factor authentication credentials",
1177
- titleFormat: "Two-factor for {user_id}",
1178
- compactLayout: ["user_id", "created_at"],
1179
- fields: {
1180
- id: Field11.text({
1181
- label: "Two Factor ID",
1182
- required: true,
1183
- readonly: true
1184
- }),
1185
- created_at: Field11.datetime({
1186
- label: "Created At",
1187
- defaultValue: "NOW()",
1188
- readonly: true
1189
- }),
1190
- updated_at: Field11.datetime({
1191
- label: "Updated At",
1192
- defaultValue: "NOW()",
1193
- readonly: true
1194
- }),
1195
- user_id: Field11.text({
1196
- label: "User ID",
1197
- required: true
1198
- }),
1199
- secret: Field11.text({
1200
- label: "Secret",
1201
- required: true,
1202
- description: "TOTP secret key"
1203
- }),
1204
- backup_codes: Field11.textarea({
1205
- label: "Backup Codes",
1206
- required: false,
1207
- description: "JSON-serialized backup recovery codes"
1208
- })
1209
- },
1210
- indexes: [
1211
- { fields: ["user_id"], unique: true }
1212
- ],
1213
- enable: {
1214
- trackHistory: false,
1215
- searchable: false,
1216
- apiEnabled: true,
1217
- apiMethods: ["get", "create", "update", "delete"],
1218
- trash: false,
1219
- 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;
1220
779
  }
1221
- });
780
+ };
1222
781
 
1223
- // src/objects/sys-user-preference.object.ts
1224
- import { ObjectSchema as ObjectSchema12, Field as Field12 } from "@objectstack/spec/data";
1225
- 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,
1226
827
  namespace: "sys",
1227
- name: "user_preference",
1228
- label: "User Preference",
1229
- pluralLabel: "User Preferences",
1230
- icon: "settings",
1231
- isSystem: true,
1232
- description: "Per-user key-value preferences (theme, locale, etc.)",
1233
- titleFormat: "{key}",
1234
- compactLayout: ["user_id", "key"],
1235
- fields: {
1236
- id: Field12.text({
1237
- label: "Preference ID",
1238
- required: true,
1239
- readonly: true
1240
- }),
1241
- created_at: Field12.datetime({
1242
- label: "Created At",
1243
- defaultValue: "NOW()",
1244
- readonly: true
1245
- }),
1246
- updated_at: Field12.datetime({
1247
- label: "Updated At",
1248
- defaultValue: "NOW()",
1249
- readonly: true
1250
- }),
1251
- user_id: Field12.text({
1252
- label: "User ID",
1253
- required: true,
1254
- maxLength: 255,
1255
- description: "Owner user of this preference"
1256
- }),
1257
- key: Field12.text({
1258
- label: "Key",
1259
- required: true,
1260
- maxLength: 255,
1261
- description: "Preference key (e.g., theme, locale, plugin.ai.auto_save)"
1262
- }),
1263
- value: Field12.json({
1264
- label: "Value",
1265
- description: "Preference value (any JSON-serializable type)"
1266
- })
1267
- },
1268
- indexes: [
1269
- { fields: ["user_id", "key"], unique: true },
1270
- { fields: ["user_id"], unique: false }
1271
- ],
1272
- enable: {
1273
- trackHistory: false,
1274
- searchable: false,
1275
- apiEnabled: true,
1276
- apiMethods: ["get", "list", "create", "update", "delete"],
1277
- trash: false,
1278
- mru: false
1279
- }
1280
- });
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
+ };
1281
835
 
1282
836
  // src/auth-plugin.ts
1283
837
  var AuthPlugin = class {
@@ -1308,43 +862,27 @@ var AuthPlugin = class {
1308
862
  });
1309
863
  ctx.registerService("auth", this.authManager);
1310
864
  ctx.getService("manifest").register({
1311
- id: "com.objectstack.system",
1312
- name: "System",
1313
- version: "1.0.0",
1314
- type: "plugin",
1315
- namespace: "sys",
1316
- objects: [
1317
- SysUser,
1318
- SysSession,
1319
- SysAccount,
1320
- SysVerification,
1321
- SysOrganization,
1322
- SysMember,
1323
- SysInvitation,
1324
- SysTeam,
1325
- SysTeamMember,
1326
- SysApiKey,
1327
- SysTwoFactor,
1328
- SysUserPreference
1329
- ]
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]
1330
885
  });
1331
- try {
1332
- const setupNav = ctx.getService("setupNav");
1333
- if (setupNav) {
1334
- setupNav.contribute({
1335
- areaId: "area_administration",
1336
- items: [
1337
- { id: "nav_users", type: "object", label: "Users", objectName: "user", icon: "users", order: 10 },
1338
- { id: "nav_organizations", type: "object", label: "Organizations", objectName: "organization", icon: "building-2", order: 20 },
1339
- { id: "nav_teams", type: "object", label: "Teams", objectName: "team", icon: "users-round", order: 30 },
1340
- { id: "nav_api_keys", type: "object", label: "API Keys", objectName: "api_key", icon: "key", order: 40 },
1341
- { id: "nav_sessions", type: "object", label: "Sessions", objectName: "session", icon: "monitor", order: 50 }
1342
- ]
1343
- });
1344
- ctx.logger.info("Auth navigation items contributed to Setup App");
1345
- }
1346
- } catch {
1347
- }
1348
886
  ctx.logger.info("Auth Plugin initialized successfully");
1349
887
  }
1350
888
  async start(ctx) {
@@ -1367,7 +905,8 @@ var AuthPlugin = class {
1367
905
  const configuredUrl = this.options.baseUrl || "http://localhost:3000";
1368
906
  const configuredOrigin = new URL(configuredUrl).origin;
1369
907
  const actualUrl = `http://localhost:${actualPort}`;
1370
- if (configuredOrigin !== actualUrl) {
908
+ const configuredIsLocalhost = configuredOrigin.startsWith("http://localhost");
909
+ if (configuredIsLocalhost && configuredOrigin !== actualUrl) {
1371
910
  this.authManager.setRuntimeBaseUrl(actualUrl);
1372
911
  ctx.logger.info(
1373
912
  `Auth baseUrl auto-updated to ${actualUrl} (configured: ${configuredUrl})`
@@ -1425,6 +964,28 @@ var AuthPlugin = class {
1425
964
  );
1426
965
  }
1427
966
  const rawApp = httpServer.getRawApp();
967
+ rawApp.get(`${basePath}/config`, (c) => {
968
+ try {
969
+ const config = this.authManager.getPublicConfig();
970
+ return c.json({ success: true, data: config });
971
+ } catch (error) {
972
+ const err = error instanceof Error ? error : new Error(String(error));
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 });
987
+ }
988
+ });
1428
989
  rawApp.all(`${basePath}/*`, async (c) => {
1429
990
  try {
1430
991
  const response = await this.authManager.handleRequest(c.req.raw);
@@ -1452,14 +1013,43 @@ var AuthPlugin = class {
1452
1013
  );
1453
1014
  }
1454
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
+ }
1455
1021
  ctx.logger.info(`Auth routes registered: All requests under ${basePath}/* forwarded to better-auth`);
1456
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
+ }
1457
1040
  };
1458
1041
  export {
1459
1042
  AUTH_ACCOUNT_CONFIG,
1043
+ AUTH_DEVICE_CODE_SCHEMA,
1460
1044
  AUTH_INVITATION_SCHEMA,
1045
+ AUTH_JWKS_SCHEMA,
1461
1046
  AUTH_MEMBER_SCHEMA,
1462
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,
1463
1053
  AUTH_ORGANIZATION_SCHEMA,
1464
1054
  AUTH_ORG_SESSION_FIELDS,
1465
1055
  AUTH_SESSION_CONFIG,
@@ -1469,24 +1059,12 @@ export {
1469
1059
  AUTH_TWO_FACTOR_USER_FIELDS,
1470
1060
  AUTH_USER_CONFIG,
1471
1061
  AUTH_VERIFICATION_CONFIG,
1472
- SysAccount as AuthAccount,
1473
1062
  AuthManager,
1474
1063
  AuthPlugin,
1475
- SysSession as AuthSession,
1476
- SysUser as AuthUser,
1477
- SysVerification as AuthVerification,
1478
- SysAccount,
1479
- SysApiKey,
1480
- SysInvitation,
1481
- SysMember,
1482
- SysOrganization,
1483
- SysSession,
1484
- SysTeam,
1485
- SysTeamMember,
1486
- SysTwoFactor,
1487
- SysUser,
1488
- SysUserPreference,
1489
- SysVerification,
1064
+ buildDeviceAuthorizationPluginSchema,
1065
+ buildJwtPluginSchema,
1066
+ buildOauthProviderPluginSchema,
1067
+ buildOidcProviderPluginSchema,
1490
1068
  buildOrganizationPluginSchema,
1491
1069
  buildTwoFactorPluginSchema,
1492
1070
  createObjectQLAdapter,